【问题标题】:How to efficiently move underlying data from std::string to a variable of another type?如何有效地将基础数据从 std::string 移动到另一种类型的变量?
【发布时间】:2018-01-12 18:50:31
【问题描述】:

编辑:对不起大家,我不认为这个玩具示例真正反映了我的问题。我应该问的是是否有办法释放 std::string 对象的缓冲区。没有,这是有道理的。谢谢!

假设我有以下(损坏的)代码:

void get_some_data(MyCustomContainer& val)
{
    std::string mystr = some_function();
    val.m_data = &mystr[0];
}

这样不行,因为mystr指向的内存在get_some_data的末尾被释放了,val.m_data引用的内存就会失效。

如何告诉 std::string “不要在析构函数中释放内存缓冲区!” ?我不想复制数据。 MyCustomerContainer 对象将在其析构函数中处理内存释放。

【问题讨论】:

  • 我闻到了XY problem。你到底想做什么?
  • 你不能返回std::string吗?
  • 使 m_data 成为 std::string。解决方案总是相同。
  • 当您添加 SSO 时,这会变得有点复杂。您无法真正占用 SSO 缓冲区。
  • MyCustomerContainer 对象如何知道如何删除 std::string 的成员缓冲区?没有std::string::oh_by_the_way_delete_this_buffer_for_me(m_data); 可供使用。

标签: c++ c++11 c++14 c++17


【解决方案1】:

你不能在不违反规则的情况下这样做。 std::string 类不允许显式释放其所有权。事实上,由于 SBO 优化,std::string 甚至可能没有分配任何内存:

std::string str1 = "not allocating";
std::string str2 = "allocating on the heap, the string is too large";

此行为完全依赖于平台和实现。如果一个字符串没有在堆上分配它的缓冲区,则数据被放在堆栈上,不需要解除分配。

{
    std::string str1 = "not allocating";
} // no buffer freed

因此,即使有办法告诉字符串不要释放其缓冲区,也无法确定缓冲区是否在堆上进行管理。

即使有办法判断字符串是否使用堆栈,您也必须在适当的位置分配一个缓冲区作为类成员并复制其内容

传输字符串的数据并窃取其对该字符串内存资源的所有权的想法从根本上被打破,因为如果不复制就无法逃脱,仅仅是因为可能没有所有权可窃取。


如果您不想更改MyCustomContainer 的工作方式,我建议您在所有情况下都复制字符串内容:

void get_some_data(MyCustomContainer& val)
{
    std::string mystr = some_function();
    val.m_data = new char[mystr.size()];
    std::memcpy(val.m_data, mystr.data(), mystr.size());
}

相比之下,如果您允许MyCustomContainer 存储std::string,那么当通过移动字符串分配缓冲区时,您实际上可以不进行复制:

void get_some_data(MyCustomContainer& val)
{
    // let m_data be a std::string
    val.m_data = some_function();

    // The above is equivalent to this:
    // std::string mystr = some_function();
    // val.m_data = std::move(mystr);
}

移动字符串将调用移动赋值。通过移动分配,字符串实现会将mystr 的缓冲区的所有权转移到m_data。这将防止任何额外的分配。

如果mystr 没有分配,那么移动分配将简单地复制数据(因此也没有分配)。

【讨论】:

    【解决方案2】:

    解决这个问题的正确方法是:

    class MyCustomContainer {
    public:
      std::string m_data;
    };
    
    void get_some_data(MyCustomContainer& val) {
      val.m_data = some_function();
    }
    

    get_some_data 甚至可以被制成一个成员函数,这将使调用站点的使用更加容易,并且可能允许m_data 是私有的而不是暴露的。

    【讨论】:

      【解决方案3】:

      如果 .m_data 是一个 std::string,您可以利用 std::string 的移动赋值运算符:

      val.m_data = std::move(mystr);
      

      如果 m_data 不是 std::string 你很不走运,内部缓冲区是不可访问的(应该如此)。

      【讨论】:

        【解决方案4】:

        不,你不能。 std 容器只会将其托管内存(而且只是有时)放弃给 std 相同类型的容器。

        对于字符串,无论如何这都是不可能的,因为大多数实现都会进行短字符串优化并在内部存储短字符串。

        您可以将 std 字符串放入某个全局缓冲区并在清理时获取它,但这会变得异常复杂。

        【讨论】:

          【解决方案5】:

          如果您愿意,您可以使用导致未定义行为的代码,因此不应该使用它,但如果您正在处理自己的一些玩具项目,很快就会被放弃,您可以看看它是否适合您。

          // REQUIRES: str is long enough so that it is using heap,
          // std::string implementation does not use CoW implementation...
          // ...
          char* steal_memory(string&& str){
              alignas(string) char buff[sizeof(string)];
              char* stolen_memory = const_cast<char*>(str.data());
              new(buff) string(move(str)); 
              return stolen_memory;
          }
          

          如果你想处理短字符串,你应该为这种情况添加 malloc 并从缓冲区复制。

          这里的主要思想是使用placement new,它从我们的输入字符串中获取所有权,而不是在buff中调用字符串的析构函数。没有析构函数意味着没有调用 free,所以我们可以从字符串中窃取内存。

          不幸的是,在这种情况下 const_cast 是 UB,所以就像我说的,你永远不应该在严肃的代码中使用这个代码。

          【讨论】:

            【解决方案6】:

            你可以做mystrstatic

            void get_some_data(MyCustomContainer& val)
            {
                static std::string mystr;
            
                mystr = some_function();
                val.m_data = &mystr[0];
            }
            

            但是,通过这种方式,所有get_some_data() 调用只有一个mystr;所以

            get_some_data(mcc1);
            get_some_data(mcc2);
            
            // now both `mcc1.m_data` and `mcc2.m_data` point to the same value,
            // obtained from the second `some_function()` call
            

            如果您可以在编译时枚举对 get_some_data() 的调用,则可以使用模板索引区分您的 mystr

            template <std::size_t>
            void get_some_data(MyCustomContainer& val)
            {
                static std::string mystr;
            
                mystr = some_function();
                val.m_data = &mystr[0];
            }
            
            get_some_data<0U>(mcc1);
            get_some_data<1U>(mcc2);
            
            // now `mcc1.m_data` and `mcc2.m_data` point to different values
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2020-03-09
              • 2012-04-11
              • 2015-07-03
              • 2021-08-18
              • 1970-01-01
              • 1970-01-01
              • 2017-11-24
              • 1970-01-01
              相关资源
              最近更新 更多