C++11:&&右值引用、std::ref与std::unique_ptr


Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383

非常容易混淆的三种语法,在新手看来含义似乎相同,都是在函数调用上参数的传递,但实际上有非常大的区别。下面我们来具体分析分析。
首先是&&右值引用,在说这个之前先得弄清什么是右值。右值也叫将亡值(这翻译简直(¬_¬)),顾名思义也就是说将会死亡的值。比如类似这样的代码:

1
std::string s = std::string("abc");

从原理上来说,这句代码的含义是,首先在等号右侧构建一个临时字符串对象,然后将右侧对象执行拷贝构造,拷贝至变量s中,然后释放等号右侧对象(实际的实现中,编译器通常会对这样的代码进行优化,直接在变量s中构建字符串对象)。
如果上面的例子不太明显那么再来一个例子,Gdi+中经常会出现这样的代码:

1
2
Gdiplus::Graphics g(&img);
g.DrawImage(&img2, &Gdiplus::Rect(0, 0, 10, 10), ...);

似曾相识的代码对吧?这儿的DrawImage函数在执行前首先构造匿名Gdiplus::Rect对象,然后作为参数传递,函数调用返回时,也就是这行代码执行完时,匿名对象将被释放。这也是它被叫做将亡值的原因。
既然这种对象在使用时生命周期短,拷贝至左值又可能会影响效率,那有没更好的引用方式呢?当然有,这叫右值引用。

1
2
3
4
5
6
7
8
9
#include <iostream>
 
int main (int argc, char* argv []) {
    int &&i = std::move (1);
    std::cout << i << std::endl;
    i = 2;
    std::cout << i << std::endl;
    return 0;
}

代码运行结果为1、2。原理就是,通过std::move移动语义,将右值“1”的寿命延长,在当前行执行完之后还可以继续使用,寿命在引用变量释放时结束。右值引用也可以传递,不过需要注意的是,一旦传递之后,原始变量就不能使用这个对象了。
另外需要注意的是,移动语义的引用不能放在当前函数块之外继续使用。比如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <string>
using namespace std;
 
class A {
public:
    A () { cout << "new object" << endl; }
    ~A () { cout << "delete object" << endl; }
};
 
A&& func () {
    return move (A ());
}
 
int main (int argc, char* argv []) {
    A&& a = func ();
    cout << "function end" << endl;
    return 0;
}

代码执行结果为:
20161030125609
由此可见,对象虽然返回了右值引用,但在进入main作用域前就已经被释放掉了。所以这东西在栈内存上酌情使用。
下面我说说std::ref这个东西,顾名思义,引用,不过这个东西和普通的参数引用有什么区别呢?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <functional>
 
int main (int argc, char* argv []) {
    auto f = [] (int &i) { i += 1; };
    int x = 1;
    auto f2 = std::bind (f, x);
    f2 ();
    std::cout << x << std::endl;
    return 0;
}

如果遇到了这样的代码,可能有些人就完全找不着北了。lambda使用引用参数,然后将其与变量x绑定,讲道理结果应该为2啊,但很神奇的,结果为1。产生这个神奇现象的原因是,std::bind内部构造的问题。std::bind通常作用域多线程领域,假如需要一个std::string参数,传递的如果不是左值,那么很容易导致程序崩溃。这时候就将其拷贝一份,虽然牺牲点效率但总比出现让人摸不着头脑的bug要好。
那对于引用参数绑定如何传递呢?解决方法是使用std::ref关键字,对其引用传递,将std::bind那行代码改为如下形式:

1
auto f2 = std::bind (f, std::ref (x));

这样之后,变量就能引用传递了。不出所料程序运行结果为2。
最后剩下的这个,智能指针,它主要用于自动回收内存,具体实现参考 C++11:智能指针

发布者

fawdlstty

又一只萌萌哒程序猿~~

《C++11:&&右值引用、std::ref与std::unique_ptr》上有6条评论

  1. 感觉正讲到火候突然断了,有几个问题
    - 多线程传递引用是否可以用std::bind 取代std::ref
    - std::ref是否返回的是右值引用,是否能够传入将亡值

    1. 1、std::bind用于函数及参数的绑定,std::ref用于传参时传递变量引用,所以不能。2、std::ref主要目的是将闭包内的修改后的参数变量通过引用给传递出去,但右值移走之后变量就不能再使用了,所以无论如何也不会有这种需求。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注