C++11 移动语义move和forward完美转发实验

最近看了《Effective Morden C++》,把C++11移动语义及相关内容简单梳理了一下,并用代码进行了测试。

欢迎感兴趣的同行一起交流。

#include <vector>

#include <testngpp/testngpp.hpp>

using namespace std;

namespace {
    // 传入左值时,会走这个重载函数
    template<typename T>
    const char* get_type(T &) {
        return "lvalue";
    }

    // 传入右值时,会走这个重载函数
    template<typename T>
    const char* get_type(T &&) {
        return "rvalue";
    }

    template<typename T>
    const char* UniversalRefFuncParam(T&& param) {
        return get_type(param); // 参数永远是左值
    }

    template<typename T>
    const char* UniversalRefFuncForward(T&& param) {
        // 传给UniversalRefFuncForward的参数的左右值类型,会forward给get_type
        // 原理是:根据推导出的T的类型
        //        如果 实参 是 左值, 则传给通用引用时,推导出的 T 为左值引用
        //        如果 实参 是 右值, 则传给通用引用时,推导出的 T 为右值引用
        //        然后根据 引用折叠 规则,只有两个引用都是右值引用,结果才是右值引用;否则是左值引用
        //        就得到 forward 转换后的类型, 左值引用,或者右值引用, 保留了传入的实参的左右值类型
        return get_type(std::forward<T>(param)); 
    }

    template<typename T>
    const char* UniversalRefFuncMove(T&& param) {
        return get_type(std::move(param)); // 总是把param转换为右值,再调用get_type
    }
}

FIXTURE(CppMoveIntro, C++11引入移动语义的背景)
{
    TEST(使用临时对象构造另外一个对象,直接移动,从而提高效率)
    {
        INFO("引入右值 - 临时对象,字面量,表达式计算结果等 : 它们不会多次复用,可以移动");
        INFO("引入左值 - 能够获取地址的对象,或者命名的对象 : 它们可能多次复用,所以不能随意移动");
        INFO("引入移动构造, 移动赋值 - 即参数为右值类型 如 int&&");
        INFO("引入 std::move 类型转换 - 即把参数转换为右值类型");
        INFO("引入 std::forward 类型转换 - 即把传给通用引用的参数的左右值类型,继续传递给调用的函数");
        INFO("基本概念1: 左值引用 形式 - int&, T&");
        INFO("基本概念2: 右值引用 形式 - int&&");
        INFO("基本概念3: 通用引用 形式 - T&& 且 T 需要进行类型推导");
        INFO("使用规则1: 通用引用参数再往下传时用forward转换");
        INFO("使用规则2: 右值引用参数再往下传时用move转换");
        INFO("使用规则3: 明确不再使用的对象,传递给别的函数时,才使用forward/move转换。避免移动后它的值改变了,后面又用到它。");
        INFO("参考《Effective Morden C++》 - 第五章 右值引用,Move语义和完美转发");
    }
};
FIXTURE(LValue, 典型的左值)
{
    TEST(变量是左值)
    {
        int var = 10;
        ASSERT_STREQ("lvalue", get_type(var));
    }
    TEST(字符串常量是左值 - 因为它是存储在char数组中,可以取地址)
    {
        ASSERT_STREQ("lvalue", get_type("This is test string"));
    }
    TEST(++a是左值 - a先自加,再返回a)
    {
        int a = 10;
        ASSERT_STREQ("lvalue", get_type(++a));
    }
    TEST(a++是右值 - 先返回a的值[实际是个临时变量],然后a自加)
    {
        int a = 10;
        ASSERT_STREQ("rvalue", get_type(a++));
    }

    int int_member = 10;
    int& get_ref() {
        return int_member;
    }
    TEST(返回引用的函数,它返回的是左值)
    {
        ASSERT_STREQ("lvalue", get_type(get_ref()));
    }
};
FIXTURE(RValue, 典型的右值)
{
    TEST(字面量是右值)
    {
        ASSERT_STREQ("rvalue", get_type(10));
    }
    TEST(左值强制类型转换为一个非引用的值 是 右值)
    {
        int var = 10;
        ASSERT_STREQ("rvalue", get_type(static_cast<double>(var)));
    }
    TEST(使用统一初始化构造的对象[非变量或成员定义] 是 右值)
    {
        ASSERT_STREQ("rvalue", get_type(double{}));
        ASSERT_STREQ("rvalue", get_type(std::vector<int>{}));
    }
    TEST(表达式运算结果是右值)
    {
        ASSERT_STREQ("rvalue", get_type(1+2));
        int a = 10, b = 20;
        ASSERT_STREQ("rvalue", get_type(a+b));
    }
    int get_val() {
        return 10;
    }
    TEST(返回值的函数,它返回的是右值)
    {
        ASSERT_STREQ("rvalue", get_type(get_val()));
    }
};
FIXTURE(FuncParamAlwaysLValue, 函数的参数都是左值)
{
    TEST(函数的参数都是左值,它们体现为形参变量,能取地址,当然是左值 - 跟传入的参数类型无关)
    {
        ASSERT_STREQ("lvalue", UniversalRefFuncParam(1)); // 传入的参数1,字面量,是右值
        int var = 10;
        ASSERT_STREQ("lvalue", UniversalRefFuncParam(var)); // 传入的参数var,是左值
    }
};
FIXTURE(ForwardRealParamLRValueAttribute, forward传递实参左右值类型)
{
    TEST(forward可以把调用UniversalRefFuncForward时传入的实参的左右值类型继续传递给调用的get_type)
    {
        ASSERT_STREQ("rvalue", UniversalRefFuncForward(1)); // 传入的参数1,字面量,是右值
        int var = 10;
        ASSERT_STREQ("lvalue", UniversalRefFuncForward(var)); // 传入的参数var,是左值
    }
};
FIXTURE(MoveAlwaysCastParamToRValue, move总是把param转换为右值)
{
    TEST(move实际是把对象转换为右值 - param本来是左值,调用move后转换为了右值 - 与外部传入的参数类型无关)
    {
        ASSERT_STREQ("rvalue", UniversalRefFuncMove(1)); // 传入的参数1,字面量,是右值
        int var = 10;
        ASSERT_STREQ("rvalue", UniversalRefFuncMove(var)); // 传入的参数var,是左值
    }
};

这个代码可以在 testngpp 或者 testngpp2 下编译运行。MSVC编译运行显示如下内容:

参考:

https://github.com/sinojelly/mockcpp

https://github.com/sinojelly/testngpp2

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值