我发现了意外(至少对我而言)的行为。
class A
{
char _text[100];
char* _beg;
char* _end;
public:
explicit A(char* text, size_t tsize) : _beg(&text[0]), _end(&text[std::min(tsize, 99)])
{
memcpy(_text, text, std::min(tsize, 99));
*_end = '\0';
}
inline std::string get_text()
{
return std::move(std::string(_beg, _end));
}
};
在代码的某处后,我这样做:
A* add_A(A&& a)
{
list_a.push_back(std::move(a));
return &(list_a.back());
}
std::list<A> list_a;
{
add_A(A("my_text", 7));
list_a.back().get_text(); //returns "my_text"
}
list_a.back().get_text(); //returns trash
因为只有我移动这个类(使用std::move
),并且调用被移动的对象的get_text()
,我才会得到垃圾,好像在变量_text
的移动地址发生变化之后,所以_beg
和_end
指向无处。
在std::move
之后,变量的地址是否真的可以改变(我认为move
并没有真正移动对象,它是为此发明的?)
如果可以更改,处理它的常用模式是什么(相应地更改指针)?
如果无法更改,可能会发生这种行为,因为我试图将此类对象移动到std::list
(因此在某种程度上会发生复制,它会更改变量的地址并使指针指向错误的位置)?
答案 0 :(得分:3)
在C ++中移动只是一种特殊的复制形式,您可以在其中修改要移动的对象中的数据。这就是unique_ptr
的工作方式;将指针从一个unique_ptr
对象复制到另一个,然后将原始值设置为NULL。
当您移动对象时,您正在创建一个新对象,一个从另一个对象获取其数据的对象。成员的地址不会“改变”;它只是不是同一个对象。
因为您没有编写复制/移动构造函数,这意味着编译器会为您编写一个。他们所做的只是复制每个元素。因此,新移动的对象将具有指向指向旧对象的指针。
即将被摧毁的物品。
这就像进入一个看起来与旧的房子相同的房子。无论它看起来像你的老房子多少,它都不是。你仍然需要改变你的地址,因为这是一所新房子。 <{1}}和_beg
的地址也必须更新。
现在,您可以创建一个移动构造函数/赋值运算符(以及一个复制构造函数/赋值运算符)来更新指针。但坦率地说,这只是贴在糟糕的设计上。如果可以帮助它,那么在同一个对象中指向子对象并不是一个好主意。而不是开始/结束指针,只需要一个实际的大小:
_end
这样,就不需要存储开始/结束指针。这些可以根据需要合成。
答案 1 :(得分:2)
std::move
没有可移动的部分,它只是将输入参数提升为右值引用 - 请记住,在foo(T&& t) { ... }
的主体内部,t
按名称使用评估为左值(参考右值)。
inline std::string get_text()
{
return std::move(std::string(_beg, _end));
}
打破这个局面:
std::string(_beg, _end);
创建一个从_beg
到_end
构造的匿名临时std :: string对象。这是一个右值。
std::move(...);
强制将其提升为右值引用,并阻止编译器执行返回值优化。你想要的是
return std::string(_beg, _end);
您可能还想使用
list_a.emplace_back(std::move(a));
不幸的是,这种方法存在两个缺陷。
更简单的是,术语moving
可能有点误导,听起来很单一。但实际上它通常是双向交换:两个对象交换属性,这样当临时对象超出范围时,它会执行以前拥有的其他对象的清理:
struct S {
char* s_;
S(const char* s) : s_(strdup(s)) {}
~S() { release(); }
void release() { if (s_) free(s_); }
S(const S& s) : s_(strdup(s.s_)) {}
S(S&& s) : s_(s.s_) { s.s_ = nullptr; }
S& operator=(const S& s) { release(); s_ = strdup(s); return *this; }
S& operator=(S&& s) { std::swap(s_, s.s_); return *this; }
};
请注意以下这一行:
S& operator=(S&& s) { std::swap(s_, s.s_); return *this; }
当我们写:
S s1("hello");
s1 = S("world");
第二行调用move-assignment运算符。 hello
副本的指针移入临时,临时超出范围并被销毁,“hello”的副本被释放。
使用您的字符数组进行此交换的效率远低于单向副本:
struct S {
char s_[100];
S(const S& s) {
std::copy(std::begin(s.s_), std::end(s.s_), std::begin(s_));
}
S(S&& s) {
char t_[100];
std::copy(std::begin(s.s_), std::end(s.s_), std::begin(t_));
std::copy(std::begin(s_), std::end(s_), std::begin(s.s_));
std::copy(std::begin(t_), std::end(t_), std::end(s_));
}
};
你没有有来执行此操作,rvalue参数只需要处于安全销毁状态,但上面是默认移动操作符是什么要去做。
代码的灾难部分是默认移动运算符是天真的。
struct S {
char text_[100];
char *beg_, *end_;
S() : beg_(text_), end_(text_ + 100) {}
};
考虑以下复制构造:
S s(S());
s.beg_
指向什么?
答案:它指向S().text_
,而不是s.text_
。您需要编写一个复制构造函数,复制text_
的内容,然后将自己的beg_
和end_
指向自己的text_
,而不是复制源值。 / p>
移动运算符出现同样的问题:它将移动 text_
的内容,但它也会移动指针,并且不知道他们是亲戚。
您需要编写复制/移动构造函数和赋值运算符,或者您可以考虑将beg_
和end_
替换为单个size_t
大小值。
但在任何一种情况下,移动都不是你的朋友:你没有转让所有权或执行浅拷贝,你的所有数据都在你的对象中。