簡體   English   中英

用於分配/解除分配 I/O 緩沖區的現代 C++ 習慣用法

[英]Modern C++ idiom for allocating / deallocating an I/O buffer

對於 I/O 工作,我需要將 N 個字節讀入緩沖區。 N 在運行時(不是編譯時)是已知的。 緩沖區大小永遠不會改變。 緩沖區被傳遞給其他例程以進行壓縮、加密等:它只是一個字節序列,沒有比這更高的了。

在 C 中,我會使用malloc分配緩沖區,然后在完成后free它。 但是,我的代碼是現代 C++,當然沒有malloc ,並且很少有原始的newdelete :我大量使用 RAII 和shared_ptr 然而,這些技術似乎都不適合這個緩沖區。 它只是一個固定長度的字節緩沖區,用於接收 I/O 並使其內容可用。

有沒有優雅的現代 C++ 習語? 或者,對於這方面,我應該堅持使用好的 ol' malloc嗎?

基本上,您有兩個主要的 C++ 方式選擇:

  • 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_ptrstd::shared_ptr手動復制緩沖區,這清楚地記錄了您的意圖:

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

C++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]);

C++20引入了std::make_unique_for_overwrite ,它允許上面的非初始化行更簡潔地寫成:

auto buf_ptr = std::make_unique_for_overwrite<uint8_t[]>(n);

是的,很簡單:

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

我認為為此使用std::vector很常見。

在手動分配的char緩沖區上使用std::vector的好處是復制語義(用於傳遞給希望出於自身目的修改數據的函數或將數據返回給調用函數時)。

此外, std::vector知道自己的大小,從而減少了需要傳遞給處理函數的參數數量並消除了錯誤來源。

您可以完全控制如何將數據傳遞給其他函數 - 根據需要通過引用常量引用

如果您需要使用普通的char*和長度調用舊的c 樣式函數,您也可以輕松地做到這一點:

// 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());
    }
}

采用

std::vector<char> buffer(N)

如果緩沖區的大小永遠不會改變,您可以通過執行以下操作將其用作數組:

char * bufPtr = &buffer[0];

這也適用於 C++03。 有關為什么這是安全的詳細信息,請參閱此評論https://stackoverflow.com/a/247764/1219722

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM