如何有效地将底层数据从 std::string 移动到其他类型的变量?
How to efficiently move underlying data from std::string to a variable of another type?
编辑:对不起大家,我不认为这个玩具的例子真正反映了我的问题。我应该问的是是否有办法释放 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 "不要在析构函数处释放内存缓冲区!我不想复制数据。对象将在其析构函数处处理内存释放。
如果不违反规则,您就无法做到这一点。不允许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
没有分配,则移动分配将仅复制数据(因此也不会在那里分配)。
解决此问题的正确方法是:
class MyCustomContainer {
public:
std::string m_data;
};
void get_some_data(MyCustomContainer& val) {
val.m_data = some_function();
}
get_some_data
甚至可以变成成员函数,这将使调用站点的使用更加容易,并且可能允许m_data
私有而不是公开。
如果.m_data是一个 std::string,你可以利用 std::string 的移动赋值运算符:
val.m_data = std::move(mystr);
如果m_data不是 std::string,那么您几乎不走运,内部缓冲区无法访问(因为它应该是)。
不,你不能。std
容器只会放弃其托管内存(有时才放弃)给std
相同类型的容器。
对于字符串,无论如何这是不可能的,因为大多数实现都会进行短字符串优化并在内部存储短字符串。
您可以将 std 字符串放入某个地方的全局缓冲区并在清理时收获它,但这变得非常复杂。
如果你愿意,你可以使用这个导致未定义行为的代码,所以不应该使用它,但是如果你正在做一些自己的玩具项目,很快就会被放弃,你可以看看它是否适合你。
// 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并从缓冲区复制。
这里的主要思想是使用从我们的输入字符串中获取所有权的放置 new,而不是在 buff 中调用字符串上的析构函数。没有析构函数意味着没有对 free 的调用,所以我们可以从字符串中窃取内存。
不幸的是,在这种情况下const_cast是UB,所以就像我说的,你永远不应该在严肃的代码中使用此代码。
你可以做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
- std::string 的对象真的可以移动吗?
- 构造函数采用std::string_view与std::string并移动
- 在'string=string+s1'和"string+=s1"之间移动语义可以保存多少个复制操作?
- 在新线程中访问移动的 std::string
- 如何有效地将底层数据从 std::string 移动到其他类型的变量?
- std::string移动构造函数真的会移动吗
- std::move在将std::string移动到另一个线程时出错
- std::vector<std::string>的内容在被分配为 R 值时会被移动吗?
- 将 std::string 中的文本块向左移动 X 个字符
- 在 C++03 中将 std::string 移动到 boost::线程中
- 如果 std::string 从未被修改,它可以在创建后移动吗?
- 有没有办法将std::string移动到std::stringstream中
- 使用 std::string 参数和不可移动/可复制参数构建 std::map
- 快速将 CString 移动到 std::string
- 试图编写能够从std::string中移动语义的字符串类
- 从char*中移动std::string的构造函数
- 你能重用移动的 std::string 吗?
- 为什么下面的代码不调用 std::string 的移动构造函数?
- 为什么VS2008 std::string.erase()移动它的缓冲区?
- 使用std::string在内存中移动jpeg是否安全?