C++11中貌似有理的右值

简介: C++11中貌似有理的右值 ================= C++11非常重要的一个概念是引入了右值right value(rvalue)概念,这篇文章不是长篇大论rvalue的文章,而是在我阅读c++头文件type_traits时看到一些代码的由感而发的。 right value顾名思义是右值的意思,估计你能体会到这是“即将消失”的意思,如果你还不明白,可以看这个例子:

C++11中貌似有理的右值

C++11非常重要的一个概念是引入了右值right value(rvalue)概念,这篇文章不是长篇大论rvalue的文章,而是在我阅读c++头文件type_traits时看到一些代码的由感而发的。

right value顾名思义是右值的意思,估计你能体会到这是“即将消失”的意思,如果你还不明白,可以看这个例子:


Object f() 
{ 
    Object o; return o;
}

int main()
{
    Object obj = f();
}

你可以想象,f()函数返回一个临时对象值,然后把这个临时对象copy-assign给main函数中的对象obj,然后f()返回的临时对象被析构,未经优化的编译器是上述这个过程,然而现代C++允许返回值优化(RVO),实际上会传递obj的指针给f()函数,从而消除从返回值copy到obj过程的。虽然有这种优化,但是右值这个语义依然是存在的。

右值的好处

C++11引入了move概念,当一个右值被识别出来时,意味着传入的值的内容可以被转移走,从而避免copy,常用于copy构造函数和assign函数:

例如:


#include<iostream>
#include<string.h>

class Vec
{
public:
    Vec() 
    {
        p_ = nullptr;
        sz_ = 0;
    }
    
    ~Vec()
    {
        delete [] p_;
    }
    
    Vec(int sz)
    {
        p_ = new char[sz];
        sz_ = sz;
    }
    
    Vec(const Vec &other)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        // 拷贝内容
        p_ = new char[other.sz_];
        sz_ = other.sz_;
        memcpy(p_, other.p_, sz_);
    }
    
    Vec(Vec &&other) // 使用右值参数
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        // 将内容转移走
        p_ = other.p_;
        sz_ = other.sz_;
        other.p_ = nullptr;
        other.sz_ = 0;
    }
    
    Vec& operator = (const Vec &other)
    {
        std::cout << __PRETTY_FUNCTION__ << '\n';
        // 拷贝内容
        p_ = new char[other.sz_];
        sz_ = other.sz_;
        memcpy(p_, other.p_, sz_);
        return *this;
    }
    
    Vec& operator =(Vec && other) //使用右值参数
    {
        //std::cout << "Vec& operator=(Vec &&)\n";
        std::cout << __PRETTY_FUNCTION__ << '\n';
        // 将内容转移走
        p_ = other.p_;
        sz_ = other.sz_;
        other.p_ = nullptr;
        other.sz_ = 0;
        return *this;
    } 
    
    char & operator[](int idx)
    {
        return p_[idx];
    }
    
    int size() const
    {
        return sz_;
    }

    void print() {
        for (auto i = 0; i < sz_; ++i)
            std::cout << p_[i];
        std::cout << '\n';
    }
private:
    char *p_;
    int sz_;
};

Vec getVec(int sz)
{
    Vec v, v2;
    
    if (sz > 0) {
    for (auto i = 0; i < v.size(); ++i)
        v[i] = 'a'+ 1; 
        return v;
    } else {
    return v2;
    }
}

int main()
{
    Vec v1 {getVec(10)};
    Vec v2;
    v2 = getVec(10);
}

运行上述程序会打印:

c++rvalue.png

我们可以看到main函数中v1被构造时使用了右值构造函数Vec(Vec&&), 结果就是将内容做了move,而不是copy,从而提高了效率。 我在getVec中加入判断sz>0这个怪异的代码是故意刁难编译器,让它无法做返回值优化(RV0),否则你连这个copy构造调用都看不到,都被编译器优化掉了.因为从getVec()返回的值在复制给v1后就消失了,所以返回值的内容被move的结果是合理的,既然它将消失,我直接就偷了它的内容.注意平时做人可不能这样,这涉及人品问题,还是先沟通打个招呼为好.

而main函数中对v2的赋值就更加明显了,先是v2使用缺省构造函数,完成构造,然后是通过
Vec& Vec::operator=(Vec&&) move assign把返回值的内容move进v2里面,原因与上面相同.

从上面的例子可以看出,C++11的右值支持确实可以提高效率,当把class做成可以move时,就不怕函数直接返回超大对象. 以前在旧的C++代码中,我们必须给函数传入一个指针或者引用作为接收内容的缓冲区,在C++11后,这种现象会减少,运算表达式看起来会更加自然.

对称性

对称性是自然的一种属性. 如果你观察上面的代码,你会发现右值只在表达式的右边或者作为函数调用的参数存在,这看起来几乎完全属于被动. 别忘记, 右值是对象时,你可以调用它的成员函数!

例如:
getVec(10).print();

那么既然时临时对象rvalue即将消失,如果我调用它的函数时也许可以做些不同事情?

C++11以前没有rvalue概念时,我们写成员函数,对this指针的修饰只有三种,例如下面
成员函数准对三种不同的对象指针this的成员函数f():


struct Test {
    void f() {
        std::cout << "normal this" << std::endl;
    }
    void f() const {
        std::cout << "normal const this" << std::endl;
    }
    void f() volatile {
        std::cout << "volatile this" << std::endl;
    }

分别对应 Test, const Test, volatile Test, 没左、右值之分。在c++11上在,这三个f()既可以使用在左值又可以使用在右值上。

C++11之后,我们有了左、右值之分,我的成员函数如何知道我是在左值对象上被调用还是右值对象上被调用?

方法是写的更加详细:


#include <iostream>

struct Test {
#if 0
    void f() {
        std::cout << "normal this" << std::endl;
    }
    void f() const {
        std::cout << "normal const this" << std::endl;
    }
    void f() volatile {
        std::cout << "volatile this" << std::endl;
    }
#else
    void f() & {  //在左值上被调用
        std::cout << "lvalue this" << std::endl;
    }

    void f() && //在右值上被调用
    {
        std::cout << "rvalue this" << std::endl;
    }

    void f() const & { //在const左值上被调用
        std::cout << "const lvalue this" << std::endl;
    }

    void f() const && { //在const右值上被调用
        std::cout << "const rvalue this" << std::endl;
    }
#endif

#if 0
    void f() volatile & {
        std::cout << "volatile lvalue this" << std::endl;;
    }
    void f() volatile && {
        std::cout << "volatile rvalue this" << std::endl;
    }
#endif
};

static Test t;
Test &l()
{
    return t;
}

const Test &l_const()
{
    return t;
}

Test r()
{
    return t;
}

const Test r_const()
{
    return t;
}

int main()
{
    l().f();
    l_const().f();
    r().f();
    r_const().f();
}

运行上述程序的结果:

c++rvalue2.png

可以看到编译器选择了分别对应于左值和右值的f()函数调用,到了这里,你可以给Vec类增加一个print2()函数,当是右值时,把内容传给一个对象,或者发起一个网络连接等疯狂的想法。

void Vec::print2() const && {
    auto s = Socket::connet("www.rvalue.com", 80);
    s.send("rvalue");
}

上面有些f函数注释掉了,你可以把它打开看看编译结果,编译器会报错,是关于函数重载和二义性,可以加深理解。

所有这些都是为了对称性,也可以说是完整性,一个右值可以出现在看起来被动的地方,也可以在成员函数主动被调用时被识别。


阅读type_traits头文件碰到的
--------------------------------------
我在阅读type_traits时碰到的is_function模板,其中有些形式如果没有上述概念,会一头雾水,现在应该容易理解了,不要被它的代码吓到,模板看起来怪异,除非特别难的,否则还是能理解的:

``C++
 
 template<typename>
    struct is_function;

/// is_function
  template<typename>
    struct is_function
    : public false_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...)>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......)>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) const>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) const &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) const &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) const>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) const &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) const &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) volatile>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) volatile &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) volatile &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) volatile>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) volatile &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) volatile &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) const volatile>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) const volatile &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes...) const volatile &&>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) const volatile>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) const volatile &>
    : public true_type { };

  template<typename _Res, typename... _ArgTypes>
    struct is_function<_Res(_ArgTypes......) const volatile &&>
    : public true_type { };

is_function模板是探测一种类型是否存在某种调用方式,它的基类true_type里面有一个静态成员value,true_type是设置成true的,而false_type是这只成false的,它们实际上是integral_constant的typedef :
http://en.cppreference.com/w/cpp/types/integral_constant
这是C++模板常用的技术。

例如我写一些例子代码来探测是否某个函数对象存在某种方式的调用,我分别对普通的this调用和const this做了判断。


template<typename T, typename ... ArgTypes>
struct use_is_function {
    void test(T &t, ArgTypes... args) {
        int v;
        if ((v = is_function<T(ArgTypes...)>::value)) {
            t(args...);
            std::cout << "is function :" << v << std::endl;
        }
    }
    void test(const T &t, ArgTypes... args) {
        int v;
        if ((v = is_function<T(ArgTypes...) const &>::value)) {
            t(args...);
            std::cout << "is function :" << v << std::endl;
        }
    }
};

struct Test
{
    int operator()(int) const
    {
        return 1;
    }
};

int main()
{
    Test t;
    use_is_function<Test, int> u;

    u.test(t, 1); // 判断是否存在 operator()(int)
    u.test((const Test &)t, 1); //判断是否存在 operator() (int) const
    return 0;
}
               

总结

C++11的右值虽然增加了学习难度,入门门槛更高,但是对于返回大对象提高运行效率和表达式变的看起来更加自然,两者之间做到了很好的结合。

目录
相关文章
|
6月前
|
算法 编译器 程序员
【C/C++ 解惑 】 std::move 将左值转换为右值的背后发生了什么?
【C/C++ 解惑 】 std::move 将左值转换为右值的背后发生了什么?
67 0
|
6月前
|
C++
c++左值和右值,左值引用和右值引用
c++左值和右值,左值引用和右值引用
53 0
|
2月前
|
编译器 C++
C++ 11新特性之右值引用
C++ 11新特性之右值引用
42 1
|
6月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
35 1
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
|
6月前
|
存储 安全 C语言
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(上)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
31 2
|
6月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(下)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
34 1
|
5月前
|
编译器 C++ 开发者
C++一分钟之-右值引用与完美转发
【6月更文挑战第25天】C++11引入的右值引用和完美转发增强了资源管理和模板灵活性。右值引用(`&&`)用于绑定临时对象,支持移动语义,减少拷贝。移动构造和赋值允许有效“窃取”资源。完美转发通过`std::forward`保持参数原样传递,适用于通用模板。常见问题包括误解右值引用只能绑定临时对象,误用`std::forward`,忽视`noexcept`和过度使用`std::move`。高效技巧涉及利用右值引用优化容器操作,使用完美转发构造函数和创建通用工厂函数。掌握这些特性能提升代码效率和泛型编程能力。
46 0
|
6月前
|
编译器 C++ 容器
【C++11(一)】右值引用以及列表初始化
【C++11(一)】右值引用以及列表初始化
|
6月前
|
存储 安全 程序员
C++11:右值引用
C++11:右值引用
41 0
|
6月前
|
存储 算法 程序员
【C++入门到精通】右值引用 | 完美转发 C++11 [ C++入门 ]
【C++入门到精通】右值引用 | 完美转发 C++11 [ C++入门 ]
53 0