将 std::span 与缓冲区一起使用 - C++

Using std::span with buffers - C++

我正在探索一种可能更安全、更方便的缓冲区处理方式,可以使用

  1. 编译时已知的固定大小
  2. 运行时大小已知

使用静态范围动态范围有什么建议?答案似乎很明显,但在使用以下示例进行测试时我感到困惑。看起来我可以通过选择如何初始化跨度来根据自己的选择来操纵范围。关于代码示例的任何想法?在示例中初始化跨度的正确方法是什么?

请注意,即使示例使用字符串,正常使用也应该是 uint8_tstd::byte。 Windows 和 MS 编译器细节并不重要。

编辑: 更新代码

#define _CRTDBG_MAP_ALLOC
#define _CRTDBG_MAP_ALLOC_NEW
// ... includes
#include <cstdlib>
#include <crtdbg.h>

enum appconsts : uint8_t { buffersize = 0xFF };

HRESULT __stdcall FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/> buffer) noexcept
{
    _RPTFN(_CRT_WARN, "FormatBuffer with size of buffer=%d, threadid=0x%8.8X\n", buffer.size(), ::GetCurrentThreadId());
    errno_t er = swprintf_s(buffer.data(), buffer.size(), L"test of buffers with std::span, buffer size: %zu", buffer.size());

    return er > 0 ? S_OK : E_INVALIDARG; 
}

extern "C" int32_t __cdecl
wmain([[maybe_unused]] _In_ int32_t argc,[[maybe_unused]] _In_reads_(argc) _Pre_z_ wchar_t* argv[]) 
{
    _RPTFN(_CRT_WARN, "Executing main thread, argc=%d, threadid=0x%8.8X\n", argc, ::GetCurrentThreadId());
    int32_t ir = 0;

    wchar_t buffer1[appconsts::buffersize];
    ir = FormatBuffer(buffer1);

    wchar_t* buffer2 = new wchar_t[appconsts::buffersize];
    ir = FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/>(buffer2, appconsts::buffersize));
    delete[] buffer2;

    std::unique_ptr<wchar_t[]> buffer3 = std::make_unique<wchar_t[]>(appconsts::buffersize);
    ir = FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/>(buffer3.get(), appconsts::buffersize));

    std::vector<wchar_t> buffer4(appconsts::buffersize);
    ir = FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/>(buffer4/*, appconsts::buffersize*/));

    _CrtDumpMemoryLeaks();
    return ir;
}

新密码 使用示例的新版本,事情会变得更清楚一些。要获得静态范围,需要在 FormatBuffer 中设置大小,只有固定的 buffer1 适合。 cppreference 文本有点混乱。下面给出动态范围。

enum appconsts : uint8_t { buffersize = 0xFF };

HRESULT __stdcall FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/> buffer) noexcept
{
    _RPTFN(_CRT_WARN, "FormatBuffer with size of buffer=%d, span extent=%d, threadid=0x%8.8X\n", 
        buffer.size(), buffer.extent == std::dynamic_extent ? -1 : buffer.extent, ::GetCurrentThreadId());

    errno_t er = swprintf_s(buffer.data(), buffer.size(), L"test of buffers with std::span, buffer size: %zu", buffer.size());

    return er > 0 ? S_OK : E_INVALIDARG; 
}

HRESULT __stdcall CreateBuffer(size_t runtimesize) noexcept
{
    _RPTFN(_CRT_WARN, "CreateBuffer with runtime size of buffer=%d, threadid=0x%8.8X\n", runtimesize, ::GetCurrentThreadId());
    HRESULT hr = S_OK;

    wchar_t buffer1[appconsts::buffersize]{};
    hr = FormatBuffer(buffer1);

    std::unique_ptr<wchar_t[]> buffer3 = std::make_unique<wchar_t[]>(runtimesize /*appconsts::buffersize*/);
    hr = FormatBuffer(std::span<wchar_t/*, runtimesize*/>(buffer3.get(), runtimesize));

    std::vector<wchar_t> buffer4(appconsts::buffersize);
    hr = FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/>(buffer4/*, appconsts::buffersize*/));

    return hr; 
}

extern "C" int32_t __cdecl 
wmain([[maybe_unused]] _In_ int32_t argc,[[maybe_unused]] _In_reads_(argc) _Pre_z_ wchar_t* argv[]) 
{
    _RPTFN(_CRT_WARN, "Executing main thread, argc=%d, threadid=0x%8.8X\n", argc, ::GetCurrentThreadId());

    //(void)argc;(void)argv;
    int32_t ir = 0;

    ir = CreateBuffer(static_cast<size_t>(argc) * appconsts::buffersize);

    return ir;
}

问题出在这里:

wchar_t* buffer2 = new wchar_t(appconsts::buffersize);
ir = FormatBuffer(std::span<wchar_t/*, appconsts::buffersize*/>(buffer2, appconsts::buffersize));
delete buffer2; // Critical error detected c0000374

new wchar_t(appconsts::buffersize) 不会创建该大小的缓冲区。它分配一个 single wchar_t 并用 appconsts::buffersize 作为值初始化它。要分配数组,请使用 new wchar_t[appconsts::buffersize]。要释放它,请使用 delete[] buffer2.

(关于运行时错误的部分已从问题中删除)

简而言之:使用动态范围,并以最简单的方式初始化,如:

wchar_t buffer1[appconsts::buffersize];
ir = FormatBuffer(buffer1);

wchar_t* buffer2 = new wchar_t[appconsts::buffersize];
ir = FormatBuffer({buffer2, appconsts::buffersize}); // won’t compile without the size
delete[] buffer2;

std::unique_ptr<wchar_t[]> buffer3 = std::make_unique<wchar_t[]>(appconsts::buffersize);
ir = FormatBuffer({buffer3.get(), appconsts::buffersize}); // won’t compile without the size

std::vector<wchar_t> buffer4(appconsts::buffersize);
ir = FormatBuffer(buffer4);

在这些示例中,如果函数需要固定范围的跨度(并且仅当该范围与数组长度完全相同时),则只有第一个可以工作。这很好,因为根据 documentation,构建一个大小错误的固定范围 std::span 是一个彻头彻尾的 UB。哎哟

固定范围跨度仅在其大小是 API 合同的一部分时才有用。比如,如果你的函数 无论如何都需要 42 个系数,那么 std::span<double, 42> 是正确的方法。但是,如果您的函数可以合理地处理任何缓冲区大小,那么硬编码今天使用的特定大小是没有意义的。