移动语义(move semantic)和完美转发(perfect forward)

完整原文链接:https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/

移动语义(move semantic)

通过移动语义,我们可以在没有必要的时候避免复制。那么在接下来,我们就重点来谈一谈移动构造函数(move constructor)。相信到这里你已经意识到了,移动构造函数的出现就是为了解决复制构造函数的这个弊病。所以,其实移动构造函数应该和复制构造函数实现差不多的功能。那么,它也应该是一种构造函数的重载(好废的废话……)。所以,我们可以想象出来,其实移动构造函数大概就会是这个样子:

Test(<KEYWORD> t):arr(t.arr){t.arr = nullptr;}

这里解释一下,通过移动构造函数,事实上我们是做了一个浅拷贝(shallow copy)。至于要将之前的指针置为空的原因在于,我们的类会在析构的时候delete掉我们的数组。那么我们浅拷贝出来的这个对象的成员变量(arr指针)就变成了一个悬挂指针(dangling pointer)。

好了,现在的问题变成了,这个<KEYWORD>究竟是什么?编译器如何自动判断到底应该调用复制构造函数(我突然想起来这个东西的翻译貌似应该是拷贝构造函数,但是既然都已经写了这么多了,我就不改了)还是移动构造函数呢?

....................

进一步探讨左值和右值

我们来考虑下面的情景:

void doWork(TYPE&& param) {
	// ops and expressions using std::move(param)
}

这个代码是从Scott Meyers的演讲当中摘取的。现在的问题是:** param是右值吗? **答案是:不!param是一个左值。

这里牵扯到一个概念,即事实上左值和右值与类型是没有关系的,即int既可以是左值,也可以是右值。区别左值和右值的唯一方法就是其定义,即能否取到地址。在这里,我们明显可以对param进行取地址操作,所以它是一个左值。也就是说,但凡有名字的“右值”,其实都是左值。这也就是为什么上面的代码当中鼓励大家对所有的变量使用std::move()转成右值的原因。

....................

完美转发(perfect forward)又是在做什么

我们依然考虑一个例子:

template <typename T>
void func(T t) {
    cout << "in func" << endl;
}

template <typename T>
void relay(T&& t) {
    cout << "in relay" << endl;
    func(t);
}

int main() {
    relay(Test());
}

在这个例子当中,我们的期待是,我们在main当中调用relayTest的临时对象作为一个右值传入relay,在relay当中又被转发给了func,那这时候转发给func的参数t也应当是一个右值。也就是说,我们希望:relay的参数是右值的时候,func的参数也是右值;当relay的参数是左值的时候,func的参数也是左值

那么现在我们来运行一下这个程序,我们会看到,结果与我们预想的似乎并不相同:

default constructor
in relay
copy constructor
in func
destructor
destructor

我们看到,在relay当中转发的时候,调用了复制构造函数,也就是说编译器认为这个参数t并不是一个右值,而是左值。这个的原因已经在上一节将结果了,因为它有一个名字。那么如果我们想要实现我们所说的,如果传进来的参数是一个左值,则将它作为左值转发给下一个函数;如果它是右值,则将其作为右值转发给下一个函数,我们应该怎么做呢?

这时,我们需要std::forward<T>()。与std::move()相区别的是,move()会无条件的将一个参数转换成右值,而forward()则会保留参数的左右值类型。所以我们的代码应该是这样:

template <typename T>
void func(T t) {
    cout << "in func " << endl;
}

template <typename T>
void relay(T&& t) {
    cout << "in relay " << endl;
    func(std::forward<T>(t));
}

现在运行的结果就成为了:

default constructor
in relay
move constructor
in func
destructor
destructor

而如果我们的调用方法变成:

int main() {
    Test t;
    relay(t);
}

那么输出就会变成:

default constructor
in relay
copy constructor
in func
destructor
destructor

完美地实现了我们所要的转发效果。

.............

................

后记

C++0x通过引入许多新的语言特性来实现了语言性能的提升,使得本来就博大精深的一门语言变得更加的难以学习。但是一旦了解,就会被语言精妙的设计所折服。参考资料中给出了更多的关于左值、右值、左值引用、右值引用、移动语义和完美转发的例子。我自己实在是没有精力看完所有的这些资料了,各位有兴趣的话可以参阅。

参考资料

  1. http://thbecker.net/articles/rvalue_references/section_01.html#section_01
  2. http://blog.csdn.net/pongba/article/details/1697636
  3. http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11
  4. https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
  5. https://onedrive.live.com/view.aspx?resid=F1B8FF18A2AEC5C5!1062

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值