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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值