如何有效地将基础数据从std :: string移动到另一种类型的变量?

时间:2018-01-12 18:50:32

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

编辑:对不起大家,我不认为这个玩具示例真的反映了我的问题。我应该问的是,是否有办法释放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对象将在其析构函数中处理内存释放。

6 个答案:

答案 0 :(得分:5)

如果不违反规则,你就无法做到这一点。 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没有分配,那么移动分配将只复制数据(因此也没有分配)。

答案 1 :(得分:3)

解决此问题的正确方法是:

class MyCustomContainer {
public:
  std::string m_data;
};

void get_some_data(MyCustomContainer& val) {
  val.m_data = some_function();
}

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

答案 2 :(得分:2)

如果.m_data是std :: string,你可以利用std :: string&#39的移动赋值运算符:

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

如果m_data不是std :: string,那么你很不幸,内部缓冲区是不可访问的(应该是这样)。

答案 3 :(得分:1)

不,你不能。 std个容器只会放弃他们的托管内存(有时只会放弃)到相同类型的std个容器。

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

你可以将std字符串抛出到某个地方的全局缓冲区中并在清理时收获它,但这会非常复杂。

答案 4 :(得分:0)

如果你想要你可以使用导致未定义行为的代码,那么就不应该使用它,但是如果你正在处理自己的一些玩具项目,那么你很快就会放弃它,你可以看看它是否适合你。 / p>

// 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和copy。

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

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

答案 5 :(得分:-3)

您可以执行mystr static

void get_some_data(MyCustomContainer& val)
{
    static std::string mystr;

    mystr = some_function();
    val.m_data = &mystr[0];
}

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

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