#c
#c
Вопрос:
У меня вообще есть эта проблема, но мой конкретный пример: при работе с .данные wav, для 16-битных волн используется целое число со знаком, тогда как 8-битные волны без знака.
Я хотел бы сделать что-то вроде следующего:
if (bytesPerSample == 2){
int16_t* buffer = new int16_t[1];
} else if (bytesPerSample == 1){
uint8_t* buffer = new uint8_t[1];
} else {
cout << "bytesPerSample: " << bytesPerSample << " is unsupported" << endl;
}
Я знаю, что это недопустимо, потому что переменная, объявленная внутри области действия оператора if, уничтожается при выходе из блока.
Я также знаю, что мог бы инициализировать оба, а затем я мог бы создать 2 оператора if и скопировать остальную часть моего кода дважды, изменить имена переменных, а затем выполнить один блок, только если #bytes равно 2, а другой, если #bytes равно 2. Однако этот метод не является кратким и может привести к тому, что я введупроблемы при изменении второго блока кода.
Существует ли какой-либо краткий способ указать использование переменной с идентичным именем, но другого типа, для нескольких возможных случаев типов?
Комментарии:
1. Рассматривали ли вы шаблоны?
2. С разными типами буфера вы обречены на различия в коде на всем протяжении. У меня, вероятно, была бы шаблонная функция и что-то вроде
if (bytesPerSample == 2){ do_stuff<int16_t>(); } else if (bytesPerSample == 1){ do_stuff<uint8_t>();} else...
3. Да, вы можете использовать шаблоны. В качестве альтернативы вы можете использовать объединения. Другой подход, который довольно распространен для аудио, заключается в чтении всего буфера как символа с правильным выравниванием, выполнении любых перестановок для исправления бесконечности, а затем переинтерпретации буфера к правильному типу.
4.
union { int16_t* i16; uint8_t* u8; } buffer;
будет храниться один указатель, который во время выполнения вы инициализируете на основе базового типа данных, и который затем вы используете только с инициализированным вами членом (в соответствии с правилами использования объединения) — обратитесь к документации . Поскольку вы всегда знаете тип данных, потому что это обязательное условие для выполнения чего-либо, это не должно быть проблемой. Это удобный способ хранить ваши данные в одном месте. Помимо этого, разумной идеей было бы использовать шаблоны функций для общих операций.5. Оберните его в класс и используйте полиморфизм. Начните с чисто
virtual
интерфейса, который определяет общие, независимые от размера выборки операции над буфером, но не владеет физическим буфером и не может быть создан. Затем создайте две реализации, которые фактически владеют буферами, с соответствующими перегрузками методов для их размеров выборки. В идеале, две реализации должны (и, вероятно, должны ) быть экземплярами общего шаблона для максимального повторного использования кода, с размером / типом выборки в качестве параметров шаблона. Или, может быть, используйтеstd::variant
intsead, который является дружественным к Cunion
.
Ответ №1:
Тип C 17 std::variant
может содержать несколько типов данных в одном пространстве. Затем вы можете использовать std::visit
для отправки на основе типа внутри.
Если вы объедините это с шаблонами, вам нужно будет написать только одну версию каждой функции, которая обрабатывает переменную.
template <typename T>
void processSamples(const std::vector<T>amp; buffer) {
/* do your processing here */
}
std::variant<
std::vector<uint8_t>,
std::vector<uint16_t>> buffer;
if (bytesPerSample == 2) {
buffer = std::vector<uint16_t>(numSamples);
} else if (bytesPerSample == 1) {
buffer = std::vector<uint8_t>(numSamples);
}
std::visit([](const autoamp; buffer) { processSamples(buffer); }, buffer);
std::visit
это не волшебство. Он использует структуру данных, аналогичную объединению с тегами: есть поле, указывающее, какой тип хранится в данный момент, и объединение с разными типами данных. При вызове std::visit
шаблонная функция генерирует, при оптимизации, что-то примерно эквивалентное коммутатору, который проверяет тип и вызывает разные функции для каждого параметра:
void processSamples(uint8_t*);
void processSamples(uint16_t*);
struct Buffer {
int bytesPerSample;
union {
uint8_t* u8;
uin16_t* u16;
}
}
Buffer buffer;
buffer.bytesPerSample = bytesPerSample;
if (buffer.bytesPerSample == 2) {
buffer.u16 = new uint16_t[16000];
} else if (buffer.bytesPerSample == 1) {
buffer.u8 = new uint8_t[16000];
}
if (buffer.bytesPerSample == 2) {
processSamples(buffer.u16);
} else if (buffer.bytesPerSample == 1) {
processSamples(buffer.u8);
}
… за исключением случаев, когда вы используете std::variant
, все типобезопасно, принимает нетривиальные типы, и вам не нужно проверять каждый блок переключения на исчерпываемость при внесении изменений.
Если у вас нет C 17, существуют реализации, совместимые с C 11: