本文主要对右值引用的赋值语句的移动过程进行了探索,看看C++能不能自动帮我们处理好对象的“移动 ”操作。
- class Ref final
- {
- public:
- std::vector<int> 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<int>{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<int>{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<int> 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<int>{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<int> m_data;
- std::vector<int>* 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<int>{1, 2, 3};
- o.m_dynamic = new std::vector<int>{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<int>{1, 2, 3};
- o.m_dynamic = new Ref{};
- o.m_dynamic->m_data = std::vector<int>{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博客