本文主要对右值引用的赋值语句的移动过程进行了探索,看看C++能不能自动帮我们处理好对象的“移动 ”操作。
class Ref final
{
public:std::vector m_data;std::string to_string() {std::stringstream ss;for (auto v: m_data) {ss << v << ", ";}return ss.str();}
};void test_move_ref() {new_section(__FUNCTION__, "");Ref o;Ref n;o.m_data = std::vector{1, 2, 3};cout << "Ref before move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;n = std::move(o);cout << "Ref after move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;
}
类 Ref 只包含了一个数组,通过 std::move,试图将 o 的数据,转移给 n,得到的日志输出为
Ref before move:
o: 1, 2, 3,
n:
Ref after move:
o:
n: 1, 2, 3,
从结果来看,是完美做到了数据的移动的。
class Cover final {
public:Ref m_ref;std::string to_string() {std::stringstream ss;ss << "cover(" << m_ref.to_string() << "), ";return ss.str();}
};void test_move_cover() {new_section(__FUNCTION__, "");Cover o;Cover n;o.m_ref.m_data = std::vector{1, 2, 3};cout << "Cover before move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;n = std::move(o);cout << "Cover after move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;
}
类 Cover 只包含了一个 Ref,通过 std::move,试图将 o 的数据,转移给 n,得到的日志输出为
Cover before move:
o: cover(1, 2, 3, ),
n: cover(),
Cover after move:
o: cover(),
n: cover(1, 2, 3, ),
从结果来看,即使是在 Ref 外再包装一层,只要没有额外的成员,也是可以完美做到数据的移动的。
class Mix final
{
public:std::vector m_data;int m_index{0};std::string to_string() {std::stringstream ss;for (auto v: m_data) {ss << v << ", ";}ss << ": " << m_index;return ss.str();}
};void test_move_mix() {new_section(__FUNCTION__, "");Mix o;Mix n;o.m_data = std::vector{1, 2, 3};o.m_index = 5;cout << "Mix before move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;n = std::move(o);cout << "Mix after move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;
}
类 Mix 包含了一个数组,还有一个数值类型成员。通过 std::move,试图将 o 的数据,转移给 n,得到的日志输出为
Mix before move:
o: 1, 2, 3, : 5
n: : 0
Mix after move:
o: : 5
n: 1, 2, 3, : 5
从结果来看,数组这部分数据,是完美做到数据的移动的,然而数值类型成员,仅仅做到了拷贝。
class MixAddr final
{
public:std::vector m_data;std::vector* m_dynamic{nullptr};std::string to_string() {std::stringstream ss;for (auto v: m_data) {ss << v << ", ";}ss << ": ";if (nullptr == m_dynamic) {ss << "nullptr";} else {ss << m_dynamic << "(";for (auto v: *m_dynamic) {ss << v << ", ";}ss << ")";}return ss.str();}~MixAddr() {if (nullptr != m_dynamic) {// // deleting m_dynamic will cause program crash// // because 'n = std::move(o)' only copy m_dynamic,// // but only one instance of std::vector is created,// // so 'delete m_dynamic' will be executed twice,// // this will trigger program crash// delete m_dynamic;m_dynamic = nullptr;}}
};void test_move_mixaddr() {new_section(__FUNCTION__, "");MixAddr o;MixAddr n;o.m_data = std::vector{1, 2, 3};o.m_dynamic = new std::vector{5, 6, 7};cout << "MixAddr before move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;n = std::move(o);cout << "MixAddr after move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;
}
类 MixAddr 包含了一个数组,还有一个数组指针。通过 std::move,试图将 o 的数据,转移给 n,得到的日志输出为
MixAddr before move:
o: 1, 2, 3, : 0000024984E42370(5, 6, 7, )
n: : nullptr
MixAddr after move:
o: 1, 2, 3, : 0000024984E42370(5, 6, 7, )
n: 1, 2, 3, : 0000024984E42370(5, 6, 7, )
从结果来看,数组这部分数据没有移动,而是拷贝的;指针也是同样,仅仅做到了拷贝。
class Head final {
public:Ref m_ref;Ref* m_dynamic{nullptr};std::string to_string() {std::stringstream ss;ss << "head(" << m_ref.to_string() << "), ";if (nullptr == m_dynamic) {ss << "nullptr";} else {ss << m_dynamic << "(" << m_dynamic->to_string() << ")";}return ss.str();}~Head() {if (nullptr == m_dynamic) {// // this should cause program crash like MixAddr, but not.// // why? maybe latency of cleanup ?delete m_dynamic;m_dynamic = nullptr;}}
};void test_move_head() {new_section(__FUNCTION__, "");Head o;Head n;o.m_ref.m_data = std::vector{1, 2, 3};o.m_dynamic = new Ref{};o.m_dynamic->m_data = std::vector{4, 5, 6};cout << "Head before move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;n = std::move(o);cout << "Head after move: " << endl;cout << " o: " << o.to_string() << endl;cout << " n: " << n.to_string() << endl;
}
类 Head 包含了一个 Ref,还有一个 Ref 指针。通过 std::move,试图将 o 的数据,转移给 n,得到的日志输出为
Head before move:
o: head(1, 2, 3, ), 0000024984E42250(4, 5, 6, )
n: head(), nullptr
Head after move:
o: head(1, 2, 3, ), 0000024984E42250(4, 5, 6, )
n: head(1, 2, 3, ), 0000024984E42250(4, 5, 6, )
从结果来看,实验结果与实验四一致,Ref 数据没有移动,而是拷贝的;Ref 指针也是同样,仅仅做到了拷贝。
通过实验来看,只包含对象(非指针引用)的类,应该可以不用定义 operator= (Ref&&),即可完成数据的转移 。
不过对于工程实践来讲,如果每个类都需要去分析他包含的成员变量再考虑移动的情况,那么代码的可读性和可维护性就比较差了。
综上,如果希望支持移动赋值方式,将数据转移,那么最好明确、完整的定义 operator=(Ref&&) ,至于构造函数 Ref(Ref&&),有空可能会再来一个实验做起来。
另外,本文只进行了对比实现,分析了现象,但是并未对原理和原因做深入探究,有兴趣的朋友欢迎在评论区分享一下,共同学习。
本文的所有示例,已同步到 Github,欢迎拉下来做测试和扩展实验。
如果对C++创建实例有兴趣,可以看看之前的另一篇文章:C++对象实例创建实验_DAVIED9的博客-CSDN博客