向std::string的char*缓冲区直接写入

12

我有一个 std::string,并且有一个接受 char* 并将其写入的函数。由于 std::string::c_str()std::string::data() 返回 const char*,因此我无法使用它们。因此,我正在分配临时缓冲区,调用一个函数,并将其复制到 std::string 中。

现在我打算处理大量的信息,拷贝这个缓冲区会有明显的影响,我希望避免这种情况发生。

一些人建议使用 &str.front()&str[0],但是这样做是否会触发未定义的行为呢?


8
C++17在std::string中添加了非const的data(),但它仍然表示您不能修改缓冲区。 嗯?它在哪里说了? - ildjarn
在访问&std[0] &str.font()之前,请确保有内存,即str.size() > 0。如果你只是实例化了str,请使用str.resize() - Garf365
1
@ildjam 看起来我完全误读了那篇论文。.data() 应该可以正常工作。 - user3624760
4个回答

26

C++98/03

不可能。字符串可能使用复制时写入,因此需要处理所有读取和写入。

C++11/14

在 [string.require] 中:

basic_string 对象中的类似字符的对象必须被连续存储。也就是说,对于任何 basic_string 对象 s,当满足 0 <= n < s.size() 的所有值 n 时,标识 &*(s.begin() + n) == &*s.begin() + n 必须成立。

所以 &str.front()&str[0] 应该可以工作。

C++17

str.data()&str.front()&str[0] 都可以工作。

这里 有所说明:

charT* data() noexcept;

返回值:指针 p,满足对于每个 i[0, size()] 范围内,都有 p + i == &operator[](i)

复杂度:常数时间。

要求:程序不得更改存储在 p + size() 处的值。

非 const 的 .data() 可以正常工作。

最近的草案.front() 的措辞如下:

const charT& front() const;

charT& front();

要求: !empty()

效果:等同于 operator[](0)

对于 operator[],草案中的措辞如下:

const_reference operator[](size_type pos) const;

reference operator[](size_type pos);

要求: pos <= size().

返回值: 如果 pos < size(),则返回 *(begin() + pos)。否则,返回一个类型为 charT 的对象的引用,该对象的值为 charT(),修改该对象会导致未定义行为。

异常:无。

复杂度:常数时间。

因此它使用迭代器算术运算。因此我们需要检查关于迭代器的信息。这里说:

3. basic_string是一个连续的容器([container.requirements.general])。

因此我们需要去 这里:

连续的容器是指支持随机访问迭代器([random.access.iterators])并且其成员类型 iteratorconst_iterator 是连续迭代器([iterator.requirements.general])的容器。

接着这里:

进一步满足以下要求的迭代器,对于整数值n和可解引用的迭代器值a(a + n)*(a + n)等价于*(addressof(*a) + n),被称为连续迭代器。

显然,连续迭代器是C++17新增功能,详见这些 论文

该要求可以重写为:

assert(*(a + n) == *(&*a + n));

在第二部分中,我们取消引用迭代器,然后获取它指向的值的地址,然后对其执行指针算术运算,再次取消引用,这与递增迭代器并取消引用相同。这意味着连续的迭代器指向存储每个值的内存,这些值紧挨着彼此存储,因此是连续的。由于接受 char* 的函数希望得到连续的内存,因此您可以将 &str.front()&str[0] 的结果传递给这些函数。

好的回答。只是需要补充一点:自C++11以来,std::string是一个连续的内存容器。因此,你只能在C++11及以后版本中安全地使用&str[0](在此之前,这取决于标准库的实现)。在C++11之前,你必须使用临时的std::vector并将其复制到std::string中,或者用它来实例化一个std::string - Garf365
2
鉴于operator[]的定义中指出“修改对象会导致未定义行为”,我认为你的答案是错误的。我认为这是标准中的缺陷。 - Martin Bonner supports Monica
@Garf365:实际上,自C++98以来,没有一个标准库不为std::string提供连续的内存,因此它一直是安全的。而且C++11规定它将永远是安全的。 - Martin Bonner supports Monica
1
@MartinBonner,它说只有在pos==size的情况下才是如此。换句话说,您不应该触及终止符号。它之前的所有内容都是公平竞争的。 - Revolver_Ocelot
我认为我误读了operator[]的定义。未定义的行为仅适用于pos不是<size()的情况(根据“要求”部分,这意味着pos == size())。 - Martin Bonner supports Monica
显示剩余5条评论

3
你可以简单地使用&s[0]来表示非空字符串,这会给你一个指向缓冲区开头的指针。
当你使用它来存放一个长度为n的字符串时,string的长度(而不仅仅是容量)需要在此之前至少为n,因为没有办法在不破坏数据的情况下将其增加。
也就是说,使用方法如下:
auto foo( int const n )
    -> string
{
    if( n <= 0 ) { return ""; }

    string result( n, '#' );   // # is an arbitrary fill character.
    int const n_stored = some_api_function( &result[0], n );
    assert( n_stored <= n );
    result.resize( n_stored );
    return result;
}

自C++11起,这种方法已正式工作。在此之前,在C++98和C++03中,缓冲区未被正式保证为连续的。但是,对于实践中的情况,自C++98以来,该方法已经工作 – 这是可以在C++11中采用连续缓冲区要求的原因(我认为这是在Lillehammer会议上添加的,那是2005年),因为不存在具有非连续字符串缓冲区的现存标准库实现。
关于
“C++17添加了可变的非常量data()到std::string,但仍然说你不能修改缓冲区。”
我不知道有任何这样的措辞,而且由于这将违反非常量data()的目的,我怀疑这个说法是不正确的。
关于
“现在我计划处理大量信息,复制这个缓冲区将产生明显的影响,我想避免它。”
如果复制缓冲区会产生明显的影响,那么您应该避免无意中复制std::string。
一种方法是将其包装在一个不可复制的类中。

0

我不知道你打算用那个 string 做什么,但如果你只需要一个能够自动释放
内存的字符缓冲区,那么我通常使用 vector<char> 或者 vector<int>
或者其他类型的缓冲区。

使用 v 作为向量,保证 &v[0] 指向一个连续的内存,
你可以将其用作缓冲区。


自C++11之前,std::string也是如此,正如MatinBonner在另一个回答的评论中所说,尽管这种行为不是标准的,但大多数标准库将std::string实现为连续的内存。 - Garf365

0

注意:如果您认为 string::front() 与 &string[0] 相同,则以下内容是多余的答案:

根据 cplusplus:在 C++98 中,您不应该写入 .data() 或 .c_str(),它们被视为只读/const:

程序不得更改此序列中的任何字符。

但在 C++11 中,这个警告被移除了,但返回值仍然是 const,因此在 C++11 中也不允许它。因此,为避免未定义行为,您可以使用 string::front(),它会返回:

如果字符串对象是 const-qualified,则函数返回 const char&。否则,它返回 char&。

所以,如果您的字符串不是 const,那么您可以正式地操作由 string::front() 返回的内容,该内容是缓冲区的第一个元素的引用。但是链接没有提到这适用于哪个 C++ 标准。我假设是 C++11 及以上。

此外,它返回的是第一个元素,而不是指针,因此您需要获取其地址。不清楚您是否被正式允许将其用作整个缓冲区的const char*,但结合其他答案,我相信它是安全的。至少它不会产生任何编译器警告。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接