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

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);
}

运行上述程序会打印:

我们可以看到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();
}

运行上述程序的结果:

可以看到编译器选择了分别对应于左值和右值的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的右值虽然增加了学习难度,入门门槛更高,但是对于返回大对象提高运行效率和表达式变的看起来更加自然,两者之间做到了很好的结合。

时间: 2024-08-30 02:28:02

C++11中貌似有理的右值的相关文章

C++11新特性:右值引用和转移构造函数

问题背景   [cpp] view plaincopy   #include <iostream>       using namespace std;       vector<int> doubleValues (const vector<int>& v)   {       vector<int> new_values( v.size() );       for (auto itr = new_values.begin(), end_itr 

c++-C++中的左值/右值引用问题

问题描述 C++中的左值/右值引用问题 int getInt() { int a = 3; return a; } int& getIntR() { int a = 3; return a; } int getRL() { return 1; } int&& getRRL() { return getRL(); } int main() { // 正确,以返回的临时变量初始化a int a = getInt(); // 正确,以返回的值初始化a int b = getIntR();

C++ 11右值引用

C++ 11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习"移动语义"(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值.       对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值.左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对 象,右值是指表达式结束时就不再存在的临时对象.一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值.下面给出一些 例子来进行说明.  i

漫谈C++11利器之右值引用(move语义&amp;Perfect Forwarding)

该文章来自阿里巴巴技术协会(ATA) 作者:空溟  C++11(2011)标准推出已经很长时间了,最接地气的特性就要属"右值引用"了(Rvalue Reference),它实现了move语义和完美转发(Perfect Forwarding),一开始觉得不好理解,所以一直想对其做一个总结.网上也有很多牛人已经做了细致的分析,但基本都是讲原理的多,本文就从Rvalue Reference引入动机入手,举例说明右值引用的使用场景,从而引出move语义和完美转发. 1. 右值引用动机: 从一个

《深入理解C++11:C++ 11新特性解析与应用》——3.3 右值引用:移动语义和完美转发

3.3 右值引用:移动语义和完美转发 类别:类作者 3.3.1 指针成员与拷贝构造 对C++程序员来说,编写C++程序有一条必须注意的规则,就是在类中包含了一个指针成员的话,那么就要特别小心拷贝构造函数的编写,因为一不小心,就会出现内存泄露.我们来看看代码清单3-16中的例子. 在代码清单3-16中,我们定义了一个HasPtrMem的类.这个类包含一个指针成员,该成员在构造时接受一个new操作分配堆内存返回的指针,而在析构的时候则会被delete操作用于释放之前分配的堆内存.在main函数中,我

c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用

为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> class HugeMem { public: HugeMem(int size) : sz(size) { pIntData = new int[sz]; } HugeMem(const HugeMem & h) : sz(h.sz) { pIntData =

C++标准之(ravalue reference) 右值引用介绍_C 语言

1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了CopyElision.RVO(包括NRVO)等编译器优化技术,它们可以防止某些情况下临时对象产生和拷贝.下面简单地介绍一下CopyElision.RVO,对此不感兴趣的可以直接跳过: (1)CopyElision CopyElision技术是为了防止某些不必要的临时对象产生和拷贝,例如: 复制代码 代码如下: structA{ A(

左值、右值与右值引用

在C语言中,我们常常会提起左值(lvalue).右值(rvalue)这样的称呼.而在编译程序时,编译器有时也会在报出的错误信息中会包含 左值.右值的说法.不过左值.右值通常不是通过一个严谨的定义而为人所知的,大多数时候左右值的定义与其判别方法是一体的.一个最为典型的判别方法就是, 在赋值表达式中,出现在等号左边的就是"左值",而在等号右边的,则称为"右值".比如: a = b + c; 在这个赋值表达式中,a就是一个左值,而b + c则是一个右值.这种识别左值.右值

浅析C++11中的右值引用、转移语义和完美转发_C 语言

1. 左值与右值:     C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值.     可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值.     从本质上理解,创建和销毁由编译器幕后控制的,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象)