Подтвердить что ты не робот

Современная идиома С++ для выделения/освобождения буфера ввода-вывода

Для работы ввода-вывода мне нужно прочитать N байтов в буфер. N известно во время выполнения (не время компиляции). Размер буфера никогда не изменится. Буфер передается другим подпрограммам для сжатия, шифрования и т.д.: Это просто последовательность байтов, не более того.

В C я бы выделил буфер с malloc, а затем free, когда закончите. Тем не менее, мой код - это современный С++, конечно, не malloc, и очень мало raw new и delete: я сильно использую RAII и shared_ptr. Однако ни один из этих методов не подходит для этого буфера. Это просто буфер с фиксированной длиной байтов, чтобы получать ввод-вывод и сделать его содержимое доступным.

Есть ли в этом элегантном стиле С++? Или, для этого аспекта, я должен просто придерживаться хорошего ol malloc?

4b9b3361

Ответ 1

В С++ 14 есть очень синтаксически чистый способ достижения того, что вы хотите:

size_t n = /* size of buffer */;
auto buf_ptr = std::make_unique<uint8_t[]>(n);
auto nr = ::read(STDIN_FILENO, buf_ptr.get(), n);
auto nw = ::write(STDOUT_FILENO, buf_ptr.get(), nr);
// etc.
// buffer is freed automatically when buf_ptr goes out of scope

Обратите внимание, что вышеупомянутая конструкция будет инициализировать (обнулять) буфер. Если вы хотите пропустить инициализацию, чтобы сохранить несколько циклов, вам придется использовать немного более уродливую форму, заданную lisyarus:

std::unique_ptr<uint8_t[]> buf_ptr(new uint8_t[n]);

С++ 20 вводит std::make_unique_default_init, что позволяет записать вышесказанное более кратко, как:

std::make_unique_default_init<uint8_t[]>(n);

Ответ 2

В принципе, у вас есть два основных варианта выбора на С++:

  • std::vector
  • std::unique_ptr

Я бы предпочел второй, так как вам не нужно все автоматическое изменение размера в std::vector, и вам не нужен контейнер - вам нужен только буфер.

std::unique_ptr имеет специализацию для динамических массивов: std::unique_ptr<int[]> вызовет delete [] в нем деструктор и предоставит вам соответствующий operator [].

Если вам нужен код:

std::unique_ptr<char[]> buffer(new char [size]);
some_io_function(buffer.get(), size); // get() returnes raw pointer

К сожалению, у него нет способа получить размер буфера, поэтому вам придется хранить его в переменной. Если это смущает вас, то std::vector выполнит работу:

std::vector<char> buffer(size);
some_io_function(buffer.data(), buffer.size()); // data() returnes raw pointer

Если вы хотите передать буфер вокруг, это зависит от того, как именно вы это делаете.

Рассмотрим следующий случай: буфер заполняется где-то, затем обрабатывается где-то еще, сохраняется в течение некоторого времени, затем записывается где-то и уничтожается. Бывает, что вам действительно не нужны два места в коде для хранения буфера, и вы можете просто std::move его с места на место. Для этого варианта использования std::unique_ptr будет работать отлично и защитит вас от случайного копирования буфера (в то время как std::vector вы можете скопировать его по ошибке, и не возникнет никаких ошибок или предупреждений).

Если, наоборот, вам нужно несколько мест в коде для хранения одного и того же буфера (возможно, он заполняется/используется/обрабатывается более чем в одном месте одновременно), вам определенно нужно std::shared_ptr. К сожалению, у него нет специализации по массиву, поэтому вам придется пройти соответствующий дебетер:

std::shared_ptr<char> buffer(new char[size], std::default_delete<char[]>());

Третий вариант: если вам действительно нужно скопировать буфер. Тогда std::vector будет проще. Но, как я уже упоминал, я чувствую, что это не лучший способ. Кроме того, вы всегда можете скопировать буферную удержку на std::unique_ptr или std::shared_ptr вручную, что четко документирует ваше намерение:

std::uniqure_ptr<char[]> buffer_copy(new char[size]);
std::copy(buffer.get(), buffer.get() + size, buffer_copy.get());

Ответ 3

Да, легко:

std::vector<char> myBuffer(N);

Ответ 4

Я думаю, что его общее использование std::vector для этого.

Преимущества использования std::vector над выделенным вручную буфером char являются семантикой копирования (для передачи в функции, которые хотят изменить данные для своих целей или при возврате данных вызывающей функции).

Также std::vector знает свой собственный размер, уменьшая количество параметров, которые нуждаются в передаче функций обработки и устраняют источник ошибок.

У вас есть полный контроль над тем, как данные передаются другим функциям - либо по ссылке, либо по ссылке const.

Если вам нужно вызвать более старую функцию c-стиля с простым char* и длиной, вы тоже можете это сделать:

// pass by const reference to preserve data
void print_data(const std::vector<char>& buf)
{
    std::cout.fill('0');
    std::cout << "0x";
    for(auto c: buf)
        std::cout << std::setw(2) << std::hex << int(c);
    std::cout << '\n';
}

// pass by reference to modify data
void process_data(std::vector<char>& buf)
{
    for(auto& c: buf)
        c += 1;
}

// pass by copy to modify data for another purpose
void reinterpret_data(std::vector<char> buf)
{
    // original data not changed
    process_data(buf);
    print_data(buf);
}

void legacy_function(const char* buf, std::size_t length)
{
    // stuff
}

int main()
{
    std::ifstream ifs("file.txt");

    // 24 character contiguous buffer
    std::vector<char> buf(24);

    while(ifs.read(buf.data(), buf.size()))
    {
        // changes data (pass by reference)
        process_data(buf);

        // modifies data internally (pass by value)
        reinterpret_data(buf);

        // non-modifying function (pass by const ref)
        print_data(buf);

        legacy_function(buf.data(), buf.size());
    }
}

Ответ 5

Используйте

std::vector<char> buffer(N)

Если размер буфера никогда не изменяется, вы можете использовать его как массив, выполнив следующее:

char * bufPtr = &buffer[0];

Это будет работать и на С++ 03. Смотрите этот комментарий fooobar.com/info/99763/..., чтобы узнать, почему это безопасно.