C++右值引用赋值语句的探索
创始人
2024-03-15 21:27:29
0

本文主要对右值引用的赋值语句的移动过程进行了探索,看看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博客

相关内容

热门资讯

原创 保... 12月26日,保剑锋果断转发工作室发布的最新律师声明,正面硬刚持续发酵的出轨、妈宝男等恶意传闻,明确...
航班变动致旅游行程“缩水” 七... 近日,夏先生向红星新闻记者反映,其一行七人于今年11月12日至27日参加的“澳大利亚新西兰16日游”...
老板说 “年终奖不发了”?别慌... 年底将至,“年终奖”成为打工人最关心的话题之一。一笔丰厚的年终奖,既是对一年辛勤付出的回报,也是辞旧...
浙江一餐馆门头挂满被子床单,店... 新京报记者 赵亚楠 制作 礼牧周 ▲新京报我们视频出品(ID:wevideo) 12月26日,浙江平...
央行:加强房地产金融宏观审慎管... 近日,中国人民银行发布了《中国金融稳定报告(2025)》。报告提出,下一步,中国人民银行将继续认真贯...
欣旺达:子公司被起诉,涉案金额... 欣旺达12月26日公告,子公司欣旺达动力作为被告于12月25日收到浙江省宁波市中级人民法院送到的民事...
阿尔及利亚立法认定法国殖民是“... 【文/观察者网 陈思佳】据美联社12月25日报道,阿尔及利亚议会24日通过一项法案,正式将法国在阿尔...
汇金股份(300368)披露提... 截至2025年12月26日收盘,汇金股份(300368)报收于14.75元,较前一交易日下跌0.07...
长沙婚姻家事律师 + 刑事辩护... 在长沙生活或工作中,若遭遇婚姻家事纠纷,如离婚财产分割、抚养权争议或刑事风险,如涉嫌犯罪被调查、面临...
央行最新发布,跨国公司迎政策利... 跨国公司跨境资金管理便利化迈出新步伐。 12月26日,中国人民银行、国家外汇管理局联合发布关于跨国公...