【问题标题】:Moving a string into a vector of strings from a stringstream将字符串从字符串流中移动到字符串向量中
【发布时间】:2014-03-28 05:24:54
【问题描述】:

我有一个std::stringstream ss 和一个std::vector<string> list

我想将push_back(或emplace_backss 转到list

如何以最好的方式避免制作ss 的支持字符串的额外副本?

我的意图是立即运行ss.clear(),因为我将重新填充它(稍后附加到list...)

可能的选择:

  1. list.emplace_back(std::move(ss.str())
  2. list.emplace_back(ss.str())
  3. list.push_back(std::move(ss.str())
  4. list.push_back(ss.str())

每种情况会发生什么?在每种情况下,ss 中会保留什么? (无论如何我都会扔掉它)

this topic 让我不确定该做什么。但问题是,我并没有将字符串流相互移动,我现在只特别担心能够移动作为字符串流构建的字符串(这可能是 巨大的 ) 放到一个字符串容器的后面。显然,如果str() 的实现方式会导致一些复制或转换,那是不可避免的,但希望我想生成能够在恒定时间内将其填充到我的向量后面的代码。

看了str()的实现:

template <class _CharT, class _Traits, class _Allocator>
basic_string<_CharT, _Traits, _Allocator>
basic_stringbuf<_CharT, _Traits, _Allocator>::str() const
{
    if (__mode_ & ios_base::out)
    {
        if (__hm_ < this->pptr())
            __hm_ = this->pptr();
        return string_type(this->pbase(), __hm_, __str_.get_allocator());
    }
    else if (__mode_ & ios_base::in)
        return string_type(this->eback(), this->egptr(), __str_.get_allocator());
    return string_type(__str_.get_allocator());
} 

然后更加困惑。

【问题讨论】:

  • 你不需要调用std::move(),因为ss.str()已经返回了一个右值。
  • 如果我没记错的话,我需要一个 xvalue。 emplace_back 也许可以强制执行此操作?或者其他的东西?我的理解很模糊。 (我认为这也可能是错误的。Emplace_back 将使用 args 构建一些内容以传递给类型的 ctor...这实际上是一个复制操作。不想要。)
  • ss.str() 是一个拷贝右值,由std::vector的移动拷贝操作符移动
  • @Julius 什么是副本右值?右值引用?
  • 如果其他阅读本文的人也对 emplace_back 感到困惑,这里有一个很好的讨论:stackoverflow.com/questions/4303513/push-back-vs-emplace-back

标签: c++ string c++11 stl stringstream


【解决方案1】:

对于这种情况,想法如下:
1) ss 返回一个字符串,它是字符串流文本的内部表示的副本,字符串有一个内部缓冲区、一个指针(这就是为什么 move 对字符串有意义,尽管还有SSO
2)由于返回的字符串是一个临时值,当传递给 emplace 时,它​​将在向量中移动构造 in-place 字符串对象,因此不会有内部缓冲区的副本(只有指针和一些整数在临时字符串和新字符串之间交换,非常便宜)。此处几乎相同的内容适用于 push_back。

所以对于

  1. list.emplace_back(std::move(ss.str())
  2. list.emplace_back(ss.str())
  3. list.push_back(std::move(ss.str())
  4. list.push_back(ss.str())

所有情况都必须做几乎相同的事情(意味着它们不会复制临时字符串缓冲区)。与往常一样,分析并查看更好的方法,但我怀疑在这种情况下是否存在明显的差异。

【讨论】:

  • 你能看看我在 cmets 上为@Julius 的回答发布的大肠杆菌吗?它们表明发生了副本。也许有一个不可避免的副本总是作为str() 的一部分发生?可能是 SSO 的结果。
  • ss.str() 每次返回一个新的临时字符串。在示例代码中,您调用了 ss.str() 三次,并且每次返回具有不同内部缓冲区地址的不同字符串,这就是为什么在输出中您有不同的值。
【解决方案2】:

默认情况下,通过复制函数返回值已经是 std::moved。即

std::string herp() { return string("lalalalala"); }

std::string derp (herp ());

将使用 derp 的 std::string 移动构造函数。

正如您在下面的代码中看到的,将使用移动赋值运算符。 在内部,字符串自己的operator=(&amp;&amp;) 将与字符串ss.str() 交换将返回

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

class Herp {
    string internal;
    public:
        const Herp& operator=(const string& s)
        {
            cout<<"Standard initialize operator\n";
            internal = s;
            return *this;
        }
        const Herp& operator=(string&& s)
        {
            cout<<"Move initialize operator\n";
            internal = s;
            return *this;
        }
};

int main()
{
    Herp a;
    stringstream ss;
    ss<<string("abcdef");
    a = ss.str();
    return 0;
}

成功时间:0 内存:3428 信号:0

移动初始化操作符

更多详情here.

【讨论】:

  • 谢谢,这对我来说仍然是黑魔法,而且我最近一直在写很多的C++。还有很多要学的。现在,我对此有大约 80% 的把握,但请确认您是否会这么好心:我应该使用emplace_back,然后我可以跳过std::move,因为它基本上只是将ss.str() 转发到移动std::string 的ctor。是吗?
  • 使用push_back也会移动吗?如果我想将它推送到向量,但我仍然想访问它怎么办(即我实际上想要一个副本......)
  • 这适用于推回。根据(en.cppreference.com/w/cpp/container/vector/emplace_back),emplace back 被明确表示不使用移动运算符
  • 嗯?所以是倒退了? emplace_back 复制,push_back 采取行动?我到底应该如何从阅读文档中得出这个结论?
  • 对于push_back,它说只有在T满足MoveInsertable的要求时才使用第二个ctor,我不知道ss.str()的值是否符合这个标准。因此这个问题。对于emplace_back,好吧,绕回我原来关于emplace_back的结论,就是我不能用(因为我要移动,不是要复制或构造)
【解决方案3】:

正如已经说过的,所有四个选项最终都会做同样的事情并且是最优的——它们将str()返回的临时字符串(它是一个右值,所以应用了push_back的&amp;&amp;重载)到向量的后面。当您有一组要传递给value_type 的构造函数的参数时,emplace_back 很有用。考虑:

std::vector<std::pair<std::string, std::string>> vec;

vec.push_back(std::make_pair("Hey", "You")); // a temporary pair is created here

vec.emplace_back("Hey", "You");  // arguments are forwarded to the constructor of
                                 // pair which is constructed in place

就您的情况而言,您能做的最好的事情就是将ss 设为右值并在其上调用str()。希望stringstream 的实现足够聪明,并意识到它可以只提供其内部缓冲区而不是复制它:

list.emplace_back(std::move(ss).str());

【讨论】:

    猜你喜欢
    • 2010-10-02
    • 2020-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-25
    • 1970-01-01
    相关资源
    最近更新 更多