Шаблонные/макроподобные подходы. Это то, что вы делаете со своими ifdefs. Дело в том, что ваш код не зависит от какого-либо конкретного поставщика для этого типа и компилируется только тогда, когда указан конкретный тип.
Использование шаблонов C++ на самом деле может быть предпочтительнее, чем препроцессор C, поскольку шаблоны могут упростить специализацию частей вашего кода для того или иного конкретного типа. Код для невыбранных типов можно отключить с помощью SFINAE.
Кроме того, вы можете абстрагироваться от разницы. Например, вы можете создать ByteArrayView
, который может быть неявно сконструирован как из const QByteArray&
, так и из const MyByteArray&
, и внутренне делегировать этому источнику. Один из способов реализовать это для фиксированного числа типов — создать помеченное объединение указателей (например, std::variant<const QByteArray*, const MyByteArray*>
или пару указателей, для которых вы гарантируете, что ровно один из них не равен нулю), но для этого потребуются предварительные объявления для тех типы. В качестве альтернативы вы можете использовать стирание типа на основе динамической диспетчеризации с классом шаблона, который создает оболочки для всех аргументов, подобных массиву байтов. Предшествующий уровень техники для этой стратегии std::function
.
На практике эти подходы требуют большого количества шаблонов для небольшой ценности и значительно усложняют вашу кодовую базу, чтобы иметь дело с несколькими возможными типами. Часто предпочтительнее преобразовать в общее представление. Ваша программная система имеет компоненты с определенными границами. На этих границах преобразуйте представление данных в удобный для вас формат. Вы можете предоставить несколько потенциальных конверсий, которые вы можете защитить с помощью ifdef или шаблонов.
Здесь вам просто нужен массив байтов только для чтения. Классическое представление C для этого было бы:
void sumAllBytes(const char* bytes, size_t len);
API, ориентированный на итератор C++, будет принимать указатели начала/конца или, возможно, будет шаблонизирован по типу итератора:
void sumAllBytes(const char* begin, const char* end);
// or
template<class It>
void sumAllBytes(It begin, It end) { ... }
Решение C++20 вместо этого может использовать std::span
или std::range
.
Также может быть вполне разумно поддерживать только свой собственный тип MyByteArray
. Если пользователь хочет QByteArray
, ему придется конвертировать в ваш тип и из него. Хотя это может быть менее эффективным во время выполнения из-за необходимости копирования/распределения, большая простота полученного кода может стоить того — слишком умный код — это то место, где любят прятаться ошибки.
Рекомендую посмотреть эти видео для лучшего погружения в вопрос: