译文

VC10中的C++0x特性 Part 2 (4) : 右值引用

翻译:飘飘白云 | 2009-06-01 18:10:53 | 阅读369 | 来源

VC10中的C++0x特性 Part 2 (4) : 右值引用

 来源:vcblog 翻译:飘飘白云 kesalin@gmail.com

这个系列的第一部分( 123 )介绍了 lambda, auto static_assert
第二部分共五页:
  第一页  第二页  第三页  本页  第五页

move 语意: 可移动成员(movable member,指带 move 语意的成员)

C++0x 的标准类型(像 vector, string, regex) 都有 move 构造函数和 move 赋值函数。而且我们也已经看到了如何在我们自己的类中通过手动管理资源来实现 move 语意(像前面的 remote_integer 类)。如果类中包含可移动数据成员(像 vector, string, regex, remote_integer )时该怎么办呢?编译器不会自动帮我们自动产生 move 构造函数和 move 赋值函数,所以我们必须手动编写它们。很幸运,有了 std::move() 编写它们是很容易的。


C:Temp>type point.cpp

#include <stddef.h>

#include <iostream>

#include <ostream>

using namespace std;

 

template <typename T> struct RemoveReference {

     typedef T type;

};

 

template <typename T> struct RemoveReference<T&> {

     typedef T type;

};

 

template <typename T> struct RemoveReference<T&&> {

     typedef T type;

};

 

template <typename T> typename RemoveReference<T>::type&& Move(T&& t) {

    return t;

}

 

class remote_integer {

public:

    remote_integer() {

        cout << "Default constructor." << endl;

 

        m_p = NULL;

    }

 

    explicit remote_integer(const int n) {

        cout << "Unary constructor." << endl;

 

        m_p = new int(n);

    }

 

    remote_integer(const remote_integer& other) {

        cout << "Copy constructor." << endl;

 

        if (other.m_p) {

            m_p = new int(*other.m_p);

        } else {

            m_p = NULL;

        }

    }

 

    remote_integer(remote_integer&& other) {

        cout << "MOVE CONSTRUCTOR." << endl;

 

        m_p = other.m_p;

        other.m_p = NULL;

    }

 

    remote_integer& operator=(const remote_integer& other) {

        cout << "Copy assignment operator." << endl;

 

        if (this != &other) {

            delete m_p;

 

            if (other.m_p) {

                m_p = new int(*other.m_p);

            } else {

                m_p = NULL;

            }

        }

 

        return *this;

    }

 

    remote_integer& operator=(remote_integer&& other) {

        cout << "MOVE ASSIGNMENT OPERATOR." << endl;

 

        if (this != &other) {

            delete m_p;

 

            m_p = other.m_p;

            other.m_p = NULL;

        }

 

        return *this;

    }

 

    ~remote_integer() {

        cout << "Destructor." << endl;

 

        delete m_p;

    }

 

    int get() const {

        return m_p ? *m_p : 0;

    }

 

private:

    int * m_p;

};

 

class remote_point {

public:

    remote_point(const int x_arg, const int y_arg)

        : m_x(x_arg), m_y(y_arg) { }

 

    remote_point(remote_point&& other)

        : m_x(Move(other.m_x)),

          m_y(Move(other.m_y)) { }

 

    remote_point& operator=(remote_point&& other) {

        m_x = Move(other.m_x);

        m_y = Move(other.m_y);

        return *this;

    }

 

    int x() const { return m_x.get(); }

    int y() const { return m_y.get(); }

 

private:

    remote_integer m_x;

    remote_integer m_y;

};

 

remote_point five_by_five() {

    return remote_point(5, 5);

}

 

remote_point taxicab(const int n) {

    if (n == 0) {

        return remote_point(1, 1728);

    }

 

    remote_point ret(729, 1000);

 

    return ret;

}

 

int main() {

    remote_point p = taxicab(43112609);

 

    cout << "(" << p.x() << ", " << p.y() << ")" << endl;

 

    p = five_by_five();

 

    cout << "(" << p.x() << ", " << p.y() << ")" << endl;

}

 

C:Temp>cl /EHsc /nologo /W4 /O2 point.cpp

point.cpp

 

C:Temp>point

Unary constructor.

Unary constructor.

MOVE CONSTRUCTOR.

MOVE CONSTRUCTOR.

Destructor.

Destructor.

(729, 1000)

Unary constructor.

Unary constructor.

MOVE ASSIGNMENT OPERATOR.

MOVE ASSIGNMENT OPERATOR.

Destructor.

Destructor.

(5, 5)

Destructor.

Destructor.

 

现在你看到啦,按成员移动(memberwise move)是很容易做到的。注意, remote_point 的 move 赋值函数没有进行自我赋值检查,是因为 remote_integer 已经检查过了。也要注意到 remote_point 隐式声明的拷贝构造函数,拷贝赋值函数和析构函数都正常运作。


到现在,你应该对 move 语意已经非常熟悉了。(希望不是抓狂啊!)为了测试你新获得的这个不可思议技能,为前面的例子写一个 +() 操作符函数当作练习吧。


最后的提醒:只要你的类支持 move 语意,你就应该实现 move 构造函数和 move 赋值函数。因为不仅仅是你平常使用这些类时可从 move 语意中获利, STL 容器和算法也能从中获利,通过廉价的 move 省下昂贵的拷贝开销。


转发问题


在程序员不用写高度泛化的代码的时候,C++98/03 的 lvalue, rvalue, 引用,还有模板看起来是很完美的。假设你要写一个完全泛化的函数 outer(),这个函数的目的是将任意数目个任意类型的参数传递(也就是“转发”)给函数 inner()。已有很多不错的解决方案,比如 factory 函数 make_shared<T>(args) 是把 args 传给 T 的构造函数,然后返回 shared_ptr<T>。(这样就把 T 对象和用于对它进行引用计数的代码存储到同一块动态内存中,性能上与侵入式引用计数一样好); 而像 function<Ret(args)> 这样的包装类是把参数传给其内部存储的函数对象(functor),等等。在这篇文章里,我们只对 outer() 是如何把参数传递给 inner() 这部分感兴趣。至于 outer() 的返回类型是怎么决定的是另外的问题(有时候很简单,如 make_shared<T>(args) 总是返回 shared_prt<T>,),但要在完全搞定这个问题的一般化情况,你就要用到 C++0x的 decltype 特性了)。


如果不带参数,就不存在这样的问题,那么带一个参数情况呢?让我们尝试写个 outer() :

 

template <typename T> void outer(T& t) {

    inner(t);

}

 

问题来了,如果传给它的参数是非常量 rvalue,那我们就无法调用 outer()。如果 inner() 接收 const int& 型的参数,那 inner(5) 是可以通过编译的,但是 outer(5) 就编译不过了。因为 T 会被推导为 int, 而 int& 是不能绑定到常量 5 的。


好吧,让我们试试这个:

 

template <typename T> void outer(const T& t) {

    inner(t);

}

 

如果 inner() 接收 int& 型参数,那就会违法 const 正确性,编译都过不了。


现在,你可以重载两个分别带 T& 和 const T& 参数的 outer(),这确实管用。当你调用 outer()时,就像直接调用 inner() 一样。


可惜的是,这中方法在多参数的情况下就麻烦了(译注:要写的重载函数太多了)。你就得为每一个参数像 T1& 和 const T1&, T2& 和 const T2& 等这样进行重载,要重载的函数数目呈指数级增长。(VC9 SP1 的 tr1::bind() 就够让人感到绝望了,它为 5 个参数这么重载出了 63 个函数。如果不这么蛮干的话,没有像这里的长篇累述,我们就很难跟使用者解释为什么不能调用用 1729 这样的 ravlue 做参数的函数。为了产生出这些重载函数使用了令人作呕的预处理机制,恶心到你都不想知道它)。


在 C++98/03 中,转发问题是很严重的,而且本质上无解(必须求助于恶心的预处理机制,这会严重拖慢编译速度,还让代码变得难以阅读)。总算, rvalue 优雅地解决了这个问题。


完美转发: 模式


完美转发让你能简单而清晰地只写一个模板函数就可以转发所有的参数给任意函数,不管它带几个参数,也不管参数类型是什么。而且参数的非常量/常量, lvalue/rvalue 属性都能得以保留,让你可以像使用 inner() 一样使用 outer(),还可以和 move 语意一起用从而获得额外的好处。( C++0x 的变长模板技术解决了“任意数目”这部分,我们在这里把 N 看做任意数目)。乍看之下很神奇,实际上很简单:

 

C:Temp>type perfect.cpp

#include <iostream>

#include <ostream>

using namespace std;

 

template <typename T> struct Identity {

    typedef T type;

};

 

template <typename T> T&& Forward(typename Identity<T>::type&& t) {

    return t;

}

 

void inner(int&, int&) {

    cout << "inner(int&, int&)" << endl;

}

 

void inner(int&, const int&) {

    cout << "inner(int&, const int&)" << endl;

}

 

void inner(const int&, int&) {

    cout << "inner(const int&, int&)" << endl;

}

 

void inner(const int&, const int&) {

    cout << "inner(const int&, const int&)" << endl;

}

 

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) {

    inner(Forward<T1>(t1), Forward<T2>(t2));

}

 

int main() {

    int a = 1;

    const int b = 2;

 

    cout << "Directly calling inner()." << endl;

 

    inner(a, a);

    inner(b, b);

    inner(3, 3);

 

    inner(a, b);

    inner(b, a);

 

    inner(a, 3);

    inner(3, a);

 

    inner(b, 3);

    inner(3, b);

 

    cout << endl << "Calling outer()." << endl;

 

    outer(a, a);

    outer(b, b);

    outer(3, 3);

 

    outer(a, b);

    outer(b, a);

 

    outer(a, 3);

    outer(3, a);

 

    outer(b, 3);

    outer(3, b);

}

 

C:Temp>cl /EHsc /nologo /W4 perfect.cpp

perfect.cpp

 

C:Temp>perfect

Directly calling inner().

inner(int&, int&)

inner(const int&, const int&)

inner(const int&, const int&)

inner(int&, const int&)

inner(const int&, int&)

inner(int&, const int&)

inner(const int&, int&)

inner(const int&, const int&)

inner(const int&, const int&)

 

Calling outer().

inner(int&, int&)

inner(const int&, const int&)

inner(const int&, const int&)

inner(int&, const int&)

inner(const int&, int&)

inner(int&, const int&)

inner(const int&, int&)

inner(const int&, const int&)

inner(const int&, const int&)

 

两行!完美转发只用了两行!够简洁吧!


这个例子示范了怎么把 t1 和 t2 从 outer() 透明地转发给 inner(); inner() 可以知道它们的非常量/常量, lvalue/ravlue 属性,就像inner是被直接调用的那样。


跟 std::move() 一样, std::identify 和 std::forward() 都是在 C++<utility> 中定义的( VC10 会有, VC10 CTP中没有)。我将演示怎么来实现它们。(再次,我将交替使用 std::identity 和我的 Identity, std::forward() 和我的 Forward(),因为他们的实现是等价的。)


< 第一页  第二页  第三页  本页 第五页 >

分享:

标签:C++0x,

添加评论