在不初始化内存的情况下调整 std::string 的大小

resize std::string without initializing memory

提问人:glades 提问时间:8/30/2022 更新时间:8/28/2023 访问量:476

问:

我正在使用 C 例程写入 std::string 的数据字段。这是一个人为的例子,实际上我通过网络得到了一个非 null 终止的字符串及其大小:

#include <string>
#include <cstdio>
#include <cstring>

const char* str = "Some examplory string.."; 

int main()
{
    std::string some_str;
    some_str.resize(strlen(str));
    std::strcpy(&some_str.front(), str);
    printf(some_str.c_str());
}

现在,根据 cppref 的说法,没有重载,只是调整大小,并且不使用“\0”或其他东西初始化内存。但是,如果我所做的只是再次覆盖它,我为什么要为此付费呢?这能以某种方式避免吗?resize()

C++ C++20 标准字符串

评论

1赞 273K 8/30/2022
它即将到来:std::basic_string::resize_and_overwrite
0赞 glades 8/30/2022
@273K 谢谢!这可能意味着现在不能做哈哈
0赞 273K 8/30/2022
它受 GCC12、Clang14 和 MSVC19.31 支持。请参阅 C++23 库功能
1赞 glades 8/30/2022
@HenriqueBucher这确实可以奏效
1赞 André 8/28/2023
不要 resize() 和 strcpy()。附加一个范围怎么样: str.append( some_str.begin(), some_str.end() ); ?

答:

2赞 Ignacio Sharpe 8/28/2023 #1

对于 c++23,我们有resize_and_overwrite

对于旧标准,让我们破解它!

它在 gcc5+、clang12+、msvc 1921+ 上运行良好

// Our hacker function, resize a std::string without initializing memory
// (but still has '\0' in the end of string)
inline void resize(std::string& str, std::size_t sz);

#if __cpp_lib_string_resize_and_overwrite >= 202110L
    inline void resize(std::string& str, std::size_t sz) {
        str.resize_and_overwrite(sz, [](char*, std::size_t sz) {
            return sz;
            });
    }
#elif (defined(__clang_major__) && __clang_major__ <= 11) ||(defined(_MSC_VER)&& _MSC_VER<=1920)
    // old clang has bug in global friend function. discard it.
    // old msvc don't support visit private, discard it.
    inline void resize(std::string& str, std::size_t sz) { str.resize(sz); }
#else

#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
    template <typename Money_t, Money_t std::string::* p>
    class string_thief {
    public:
        friend void string_set_length_hacker(std::string& bank, std::size_t sz) {
            (bank.*p)(sz);
        }
    };
#elif defined(_MSVC_STL_VERSION)
    template <typename Money_t, Money_t std::string::* p>
    class string_thief {
    public:
        friend void string_set_length_hacker(std::string& bank, std::size_t sz) {
            (bank.*p)._Myval2._Mysize = sz;
        }
    };
#endif

#if defined(__GLIBCXX__)  // libstdc++
    template class string_thief<void(std::string::size_type),
        &std::string::_M_set_length>;
#elif defined(_LIBCPP_VERSION)
    template class string_thief<void(std::string::size_type),
        &std::string::__set_size>;
#elif defined(_MSVC_STL_VERSION)
    template class string_thief<decltype(std::string::_Mypair),
        &std::string::_Mypair>;
#endif

#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
    defined(_MSVC_STL_VERSION)
    void string_set_length_hacker(std::string& bank, std::size_t sz);
#endif

    inline void resize(std::string& str, std::size_t sz) {
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
    defined(_MSVC_STL_VERSION)
        str.reserve(sz);
        string_set_length_hacker(str, sz);
        str[sz] = '\0';
#else
        str.resize(sz);
#endif
    }
#endif
1赞 HolyBlackCat 8/28/2023 #2

在 C++23 中,您可以使用 std::string::resize_and_overwrite。例:

#include <cstring>
#include <iostream>
#include <string>

int main()
{
    std::string str;
    std::size_t capacity = 10; // Doesn't include the final `\0`, room for it is added automatically.
    str.resize_and_overwrite(capacity, [](char *ptr, std::size_t count)
    {
        std::strcpy(ptr, "1234567890");
        // Writing the terminating `\0` is optional, the character at `ptr[return_value]` is overwritten with `\0` anyway.
        return count; // Doesn't include the final `\0`.
    });

    std::cout << str << '\n'; // 1234567890
}

它首先分配存储字节,调用 lambda 来填充它(lambda 的第二个参数冗余地接收相同的值)。capacitycapacity

然后,lambda 返回最终字符串大小,该大小通常必须匹配,但如果 lambda 决定写入更少的字节,则可以更小。capacity

Cppreference 声明您必须初始化 lambda 中的整个字符串,否则行为是未定义的。在实践中,你是否可以不这样做(在以后填补它)是另一个问题,如果没有充分的理由,我不会冒险。

我相信不能保证它实际上用作最终字符串容量(在实践中,对于短的 SSO 字符串,它可能会有所不同)。capacity

评论

0赞 André 8/28/2023
从技术上讲,您没有回答实际问题;-)。如何在不初始化的情况下调整大小... +1 无论如何。也许您可以强调,在不覆盖的情况下调整大小是 UB(使用什么都不做的 lambda)。
0赞 HolyBlackCat 8/28/2023
̄\_(ツ)_/ ̄ @André我在倒数第二段中谈到了这一点。我会稍微调整一下措辞。