• C++右值引用赋值语句的探索


    本文主要对右值引用的赋值语句的移动过程进行了探索,看看C++能不能自动帮我们处理好对象的“移动 ”操作。

    实验一:对包含向量的类进行移动赋值

    1. class Ref final
    2. {
    3. public:
    4. std::vector<int> m_data;
    5. std::string to_string() {
    6. std::stringstream ss;
    7. for (auto v: m_data) {
    8. ss << v << ", ";
    9. }
    10. return ss.str();
    11. }
    12. };
    13. void test_move_ref() {
    14. new_section(__FUNCTION__, "");
    15. Ref o;
    16. Ref n;
    17. o.m_data = std::vector<int>{1, 2, 3};
    18. cout << "Ref before move: " << endl;
    19. cout << " o: " << o.to_string() << endl;
    20. cout << " n: " << n.to_string() << endl;
    21. n = std::move(o);
    22. cout << "Ref after move: " << endl;
    23. cout << " o: " << o.to_string() << endl;
    24. cout << " n: " << n.to_string() << endl;
    25. }

    类 Ref 只包含了一个数组,通过 std::move,试图将 o 的数据,转移给 n,得到的日志输出为 

    Ref before move:
       o: 1, 2, 3,
       n:
    Ref after move:
       o:
       n: 1, 2, 3,

    从结果来看,是完美做到了数据的移动的。 

    实验二:对级联包含向量的类进行移动赋值

    1. class Cover final {
    2. public:
    3. Ref m_ref;
    4. std::string to_string() {
    5. std::stringstream ss;
    6. ss << "cover(" << m_ref.to_string() << "), ";
    7. return ss.str();
    8. }
    9. };
    10. void test_move_cover() {
    11. new_section(__FUNCTION__, "");
    12. Cover o;
    13. Cover n;
    14. o.m_ref.m_data = std::vector<int>{1, 2, 3};
    15. cout << "Cover before move: " << endl;
    16. cout << " o: " << o.to_string() << endl;
    17. cout << " n: " << n.to_string() << endl;
    18. n = std::move(o);
    19. cout << "Cover after move: " << endl;
    20. cout << " o: " << o.to_string() << endl;
    21. cout << " n: " << n.to_string() << endl;
    22. }

    类 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 外再包装一层,只要没有额外的成员,也是可以完美做到数据的移动的。 

    实验三:对包含向量和值的类进行移动赋值

    1. class Mix final
    2. {
    3. public:
    4. std::vector<int> m_data;
    5. int m_index{0};
    6. std::string to_string() {
    7. std::stringstream ss;
    8. for (auto v: m_data) {
    9. ss << v << ", ";
    10. }
    11. ss << ": " << m_index;
    12. return ss.str();
    13. }
    14. };
    15. void test_move_mix() {
    16. new_section(__FUNCTION__, "");
    17. Mix o;
    18. Mix n;
    19. o.m_data = std::vector<int>{1, 2, 3};
    20. o.m_index = 5;
    21. cout << "Mix before move: " << endl;
    22. cout << " o: " << o.to_string() << endl;
    23. cout << " n: " << n.to_string() << endl;
    24. n = std::move(o);
    25. cout << "Mix after move: " << endl;
    26. cout << " o: " << o.to_string() << endl;
    27. cout << " n: " << n.to_string() << endl;
    28. }

    类 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

     从结果来看,数组这部分数据,是完美做到数据的移动的,然而数值类型成员,仅仅做到了拷贝。 

    实验四:对包含向量和指针的类进行移动赋值

    1. class MixAddr final
    2. {
    3. public:
    4. std::vector<int> m_data;
    5. std::vector<int>* m_dynamic{nullptr};
    6. std::string to_string() {
    7. std::stringstream ss;
    8. for (auto v: m_data) {
    9. ss << v << ", ";
    10. }
    11. ss << ": ";
    12. if (nullptr == m_dynamic) {
    13. ss << "nullptr";
    14. } else {
    15. ss << m_dynamic << "(";
    16. for (auto v: *m_dynamic) {
    17. ss << v << ", ";
    18. }
    19. ss << ")";
    20. }
    21. return ss.str();
    22. }
    23. ~MixAddr() {
    24. if (nullptr != m_dynamic) {
    25. // // deleting m_dynamic will cause program crash
    26. // // because 'n = std::move(o)' only copy m_dynamic,
    27. // // but only one instance of std::vector is created,
    28. // // so 'delete m_dynamic' will be executed twice,
    29. // // this will trigger program crash
    30. // delete m_dynamic;
    31. m_dynamic = nullptr;
    32. }
    33. }
    34. };
    35. void test_move_mixaddr() {
    36. new_section(__FUNCTION__, "");
    37. MixAddr o;
    38. MixAddr n;
    39. o.m_data = std::vector<int>{1, 2, 3};
    40. o.m_dynamic = new std::vector<int>{5, 6, 7};
    41. cout << "MixAddr before move: " << endl;
    42. cout << " o: " << o.to_string() << endl;
    43. cout << " n: " << n.to_string() << endl;
    44. n = std::move(o);
    45. cout << "MixAddr after move: " << endl;
    46. cout << " o: " << o.to_string() << endl;
    47. cout << " n: " << n.to_string() << endl;
    48. }

    类 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, )
     

     从结果来看,数组这部分数据没有移动,而是拷贝的;指针也是同样,仅仅做到了拷贝。

    实验五:对级联包含向量和指针的类进行移动赋值

    1. class Head final {
    2. public:
    3. Ref m_ref;
    4. Ref* m_dynamic{nullptr};
    5. std::string to_string() {
    6. std::stringstream ss;
    7. ss << "head(" << m_ref.to_string() << "), ";
    8. if (nullptr == m_dynamic) {
    9. ss << "nullptr";
    10. } else {
    11. ss << m_dynamic << "(" << m_dynamic->to_string() << ")";
    12. }
    13. return ss.str();
    14. }
    15. ~Head() {
    16. if (nullptr == m_dynamic) {
    17. // // this should cause program crash like MixAddr, but not.
    18. // // why? maybe latency of cleanup ?
    19. delete m_dynamic;
    20. m_dynamic = nullptr;
    21. }
    22. }
    23. };
    24. void test_move_head() {
    25. new_section(__FUNCTION__, "");
    26. Head o;
    27. Head n;
    28. o.m_ref.m_data = std::vector<int>{1, 2, 3};
    29. o.m_dynamic = new Ref{};
    30. o.m_dynamic->m_data = std::vector<int>{4, 5, 6};
    31. cout << "Head before move: " << endl;
    32. cout << " o: " << o.to_string() << endl;
    33. cout << " n: " << n.to_string() << endl;
    34. n = std::move(o);
    35. cout << "Head after move: " << endl;
    36. cout << " o: " << o.to_string() << endl;
    37. cout << " n: " << n.to_string() << endl;
    38. }

    类 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博客

  • 相关阅读:
    全网最详细的自动化测试(Jenkins 篇)
    在 Linux 程序中使用 breakpad
    【萌新解题】斐波那契数列
    最全的各版本PostGis下载
    javaweb学习(一):jsp
    Word处理控件Aspose.Words功能演示:在 Python 中将 Word 文档拆分为多个文件
    基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(一)
    sipp3.6多方案压测脚本
    企业安全—培训
    Download Quartz持久化数据库下载地址
  • 原文地址:https://blog.csdn.net/davied9/article/details/128167889