C++11 缺少using引用是用疑问

C++11的线程问题
[问题点数:20分,无满意结帖,结帖人u]
C++11的线程问题
[问题点数:20分,无满意结帖,结帖人u]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
2013年5月 高性能开发大版内专家分月排行榜第二2013年4月 高性能开发大版内专家分月排行榜第二
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。C++11 的重大改变 - 推酷
C++11 的重大改变
自从 C++ 语言第一次迭代已经过去 13 年。C++ 标准委员会成员 Danny Kalev 在本文中解释了这门编程语言有怎样的改进,以及如何帮助你编写更好的代码。
C++ 的发明者 Bjarne Stroustrup 最近说,C++11 “
——各部分之间能够更好的协作”。事实上,核心 C++11 已经有了很大的改变。现在它支持 lambda 表达式,自动类型推断,统一的初始化语法,委托构造函数,已删除和默认函数声明,nullptr,以及最重要的,右值引用——一种预言将会改变创造和处理对象方法的技术。下面我们将一一说明。
C++11 标准库同样增加了新的算法,新的容器类,原子运算,类型特性,正则表达式,新的智能指针,async() 能力以及多线程库。
C++11 的完整特性以及库的列表可以在
1998年 C++ 标准颁布之后,委员会中两个成员语言,下一个 C++ 标准“肯定”包含一个内建的垃圾回收器(GC),可能不会支持多线程,因为构建一个可移植的线程模型需要复杂的技术。13年之后,新的 C++ 标准,C++11 已经完成。猜猜发生了什么?这个标准缺少了 GC,却包含了一个现代化的线程库。
在这篇文章中,我将解释这门语言的重大改变,以及为什么把它们看出是一件大事。就像你即将看到的那样,线程库不是唯一的改变。 新的标准建立在数十年的专家建议基础之上,让 C++ 变得更加有意义。正如
指出的那样,“这就像迪斯科、Pet Rocks(一种古老的美国游戏)和大龄的奥林匹克游泳运动员一样,C++ 表现得相当令人惊喜。”
首先我们来看看 C++11 突出的核心语言特性。
Lambda 表达式
lambda 表达式允许你定义本地(也就是调用函数的地方)函数,因此可以排除掉许多函数对象导致的冗余以及安全风险。lambda 表达式格式为:
Language:&&C++
[capture](parameters)-&return-type {body}
在一个函数调用的参数列表中出现 [] 意味着开始一个 lambda 表达式。下面看一个 lambda 例子。
假设你想要计算一个字符串中有多少大写字母。使用 for_each() 遍历字符数组,下面的 lambda 表达式判断每一个字母是不是大写的。每找到一个大写字母,该 lambda 表达式将定义在表达式外的 Uppercase 变量的数值增加:
Language:&&C++
int main()
char s[] = &Hello World!&;
int Uppercase = 0; // lambda 对其进行修改
for_each(s, s+sizeof(s), [&Uppercase] (char c) {
if (isupper(c))
Uppercase++;
cout && Uppercase && & uppercase letters in: & && s && endl;
这就像你定义了一个函数,而函数体位于另外一个函数调用中。[&Uppercase] 中的 & 意味着 lambda 体以引用方式获得 Uppercase 所以能够修改它。没有 & 符号,Uppercase 将以传值的方式传入。C++11 lambda 也包含了对成员函数的构造。
自动类型推断和 decltype
在 C++03 中,你必须在声明时指定对象类型。但是在许多情况下,对象的声明包含了一个初始化器。C++11 利用这一地点,允许你以不指定类型的方式声明对象:
Language:&&C++
auto x = 0; // 因为 0 是 int,所以 x 类型是 int
auto c = 'a'; // char
auto d = 0.5; // double
auto national_debt = 00LL; //long long
当对象类型太冗长或者是模板中的自动生成时,自动类型推断尤其有用。考虑:
Language:&&C++
void func(const vector&int& &vi)
vector&int&::const_iterator ci = vi.begin();
你可以像下面一样声明这个遍历器:
Language:&&C++
auto ci = vi.begin();
auto 关键字不是新增的,早在 ANSI C 之前就已经存在。但是 C++11 改变了其含义:auto 不在意味着一个具有自动存储类型的对象。它意味着一个从其初始化器推断类型的对象。auto 旧的语义已经从 C++11 移除,以避免产生歧义。
C++11 为捕获对象或表达式的类型提供了类似的机制。新的运算符 decltype 接收一个表达式并“返回”其类型:
Language:&&C++
const vector&int& vi;
typedef decltype (vi.begin()) CIT;
CIT another_const_iterator;
统一初始化语法
C++ 至少有四种不同的初始化方法,有些是重叠的。
带有括号的初始化类似:
Language:&&C++
std::string s(&hello&);
int m = int(); // 默认初始化
在某些特殊情况,你也可以使用 = 达到相同目的:
Language:&&C++
std::string s = &hello&;
int x = 5;
对于 POD(Plain Old Data,具有 C 兼容特点)聚合,可以使用大括号:
Language:&&C++
int arr[4] = {0,1,2,3};
struct tm today = {0};
最后,在构造函数中使用成员初始化器:
Language:&&C++
S(): x(0) {}
初始化操作的多种变体是令人感觉困扰的重要原因之一,不仅仅对于新手而言。更糟糕的是,在 C++03 你不能对使用 new[] 分配空间的 POD 数组进行数组成员和 POD 本身的初始化。C++11 使用统一的大括号标记清楚了这种混乱:
Language:&&C++
C(int i, int j);
C c {0,0}; // C++11 可用,等价于 C c(0,0);
int* a = new int[3] { 1, 2, 0 }; // C++11 可用
X() : a{1,2,3,4} {} // C++11 可用,成员数组初始化器
对于容器,现在可以跟一长串 push_back() 调用说再见了。在 C++11 你可以直观地初始化容器:
Language:&&C++
// C++11 容器初始化器
vector&string& vs = { &first&, &second&, &third&};
map singers = { {&Lady Gaga&, &+1 (212) 555-7890&},
{&Beyonce Knowles&, &+1 (212) 555-0987&}};
类似的,C++11 支持数据成员的类内初始化:
Language:&&C++
int a = 7; // C++11 可用
删除和默认函数
具备如下形式的函数成为默认函数(defaulted function):
Language:&&C++
A() = default; // C++11
virtual ~A() = default; // C++11
= 部分说明,编译器生成该函数的一个默认实现。默认函数有两个优势:比手工实现更有效;把程序员从手动定义这些函数的繁琐中解放出来。
与默认函数相反的是删除函数:
Language:&&C++
int func() = delete;
删除函数对防止对象复制很有用。回忆一下,C++ 为每个类自动声明一个拷贝构造函数和赋值运算符。为禁止复制,需要将这两个特殊的成员函数声明为 =delete:
Language:&&C++
struct NoCopy
NoCopy & operator =( const NoCopy & ) = delete;
NoCopy ( const NoCopy & ) = delete;
NoCopy b(a); // 编译错误,拷贝构造函数已经被删除
终于,C++ 有了一个代表空指针常量的关键字。nullptr 替换了充满 bug 的 NULL 宏,以及被用于空指针好多年的字面常量 0。nullptr 是强类型的:
Language:&&C++
void f(int); // #1
void f(char *);// #2
f(0); // 调用哪一个 f?
f(nullptr) // 无歧义,调用 #2
nullptr 适用于所有指针类型,包括函数指针和成员指针:
Language:&&C++
const char *pc = str.c_str(); // 数据指针
if (pc != nullptr)
cout && pc && endl;
int (A::*pmf)() = nullptr; // 成员函数指针
void (*pmf)() = nullptr; // 函数指针
委托构造函数
在 C++11 中,构造函数可以调用同一个类中另外的构造函数:
Language:&&C++
class M // C++11 委托构造函数
M(int v) : x(v), y(0),& p(new char [MAX])& {} // #1 目标
M(): M(0) {cout && &delegating ctor& && endl;} // #2 委托
构造函数 #2 是委托构造函数,调用了目标构造函数 #1。
C++03 的引用类型只能绑定
。C++11 引入了新的引用类型——右值引用。右值引用可以绑定右值,例如
和字面常量。
增加右值引用的主要原因是移动语义。不同于传统的拷贝,移动的含义是目标对象占有源对象的资源,将源对象设置为“空”状态。在这种情景下,拷贝一个对象既昂贵又不必要,应该使用移动操作符。为感受移动语义在性能上的优势,考虑交换字符串。一个原始的实现类似于:
Language:&&C++
void naiveswap(string &a, string & b)
string temp = a;
这种实现很昂贵。拷贝字符串需要分配内存,将字符从源对象复制到目标对象。相比而言,移动字符串仅仅意味着交换两个数据成员,不需要分配内存、复制字符数组和释放内存:
Language:&&C++
void moveswapstr(string& empty, string & filled)
// 伪代码,体会思想
size_t sz = empty.size();
const char *p = empty.data();
// 移动 filled 的资源到 empty
empty.setsize(filled.size());
empty.setdata(filled.data());
// filled 编程空的了
filled.setsize(sz);
filled.setdata(p);
如果你正在实现一个支持移动的类,需要声明一个移动构造函数和移动复制运算符:
Language:&&C++
class Movable
Movable (Movable&&); // 移动构造函数
Movable&& operator=(Movable&&); // 移动复制运算符
C++11 标准库大量使用了移动语义。许多算法和容易也为移动语义做了优化。
C++11 标准库
2003年,C++ 以
(TR1)的形式经历了一次大型重构。TR1 包含了新的容器类(unordered_set,unordered_map,unordered_multiset 和 unordered_multimap)和许多新的库,例如正则表达式,元祖,函数对象包装器。随着 C++11 的颁布,TR1 连同新的库一起正式集成到 C++ 标准中。下面是 C++11 标准库的特性:
毫无疑问,从程序员角度看,C++11 最重要的改进就是并发。C++11 有一个 thread 类,描述一个执行线程、
(用于并发环境下同步的对象),用于发起并发任务的模板函数
和用于声明线程独立的数据的存储类型
。快速了解 C++11 线程库,请阅读 Anthony Williams 的文章
新的智能指针类
C++98 只定义了一个智能指针类,auto_ptr,而这个类现在已经被废弃了。C++11 包含了新的智能指针类:
和最近新加的
。这两个类都与其他标准库组件兼容,所以你可以安全地将这些智能指针添加到标准容易以及使用标准算法操作。
C++11 标准库定义了模拟集合论操作的新的算法 all_of()、any_of() 和 none_of()。下面几行将谓词 ispositive() 应用于范围 [first, first+n),然后使用 all_of()、any_of() 和 none_of() 检测范围的属性:
Language:&&C++
#include &algorithm&
// C++11 代码
// 所有元素都是正数吗?
all_of(first, first+n, ispositive()); // false
// 至少有一个元素是正数吗?
any_of(first, first+n, ispositive()); // true
// 没有元素是正数?
none_of(first, first+n, ispositive()); // false
还有一个新的 copy_n 算法。使用 copy_n() 将一个有 5 个元素的数组复制到另外一个可说是小菜一碟:
Language:&&C++
#include &algorithm&
int source[5] = { 0, 12, 34, 50, 80 };
int target[5];
// 从源数组到目的数组拷贝 5 个元素
copy_n(source, 5, target);
iota() 算法创建一个递增的数字范围,就像首先给 *first 赋初始值,然后使用 ++ 递增。在下面的代码中,iota() 将连续数值 {10,11,12,13,14} 赋值给数组 arr,将 {‘a’, ‘b’, ‘c’} 赋值给字符数组 c。
Language:&&C++
include &numeric&
int a[5] = {0};
char c[3] = {0};
iota(a, a+5, 10); // 将 a 修改为 { 10, 11, 12, 13, 14 }
iota(c, c+3, 'a'); // {'a', 'b', 'c'}
C++11 依然缺少一些有用的库,例如 XML API,socket,GUI,反射——当然,还是有合理的自动垃圾回收器。但是,现在 C++ 的确已经提供了很多新的特性,使得代码更加安全、高效(使得,迄今为止最高效的。参加 Google 的
)以及学习和使用起来更加简单。
如果 C++11 的改变过于宏大,不要抱怨。花些时间循序渐进地理解这些变化。在这一过程的最后,你可能就同意 Stroustrup 的弈剑:C++11 的确像是一种新的语言——一种更好的语言!
已发表评论数()
&&登&&&陆&&
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见linux下的Qt使用C++11的问题。_c++吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:163,918贴子:
linux下的Qt使用C++11的问题。收藏
我用的是Qt 5.1正式版,编译器配的是是GCC 4.7。而且我在Qt 的 pro文件中也声明了CONFIG C++11,但是Qt Creater还是编译不了C++11特性的code怎么破
先检查QT是否正确安装,再看看你编译时有没有漏了这几条命令 qmake -project / qmake /make
你看看Qt Creator的输出,调用gcc的时候到底有没有加相关的参数来确认到底是Qt的问题还是gcc的问题
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或如何评价 C++11 的右值引用(Rvalue reference)特性?
虽然它解决了一些很实际的问题,但我个人总觉得这个特性有些怪怪的。将这样一个问题留给程序员解决,有一种让本来就不算漂亮的C++语法更加臃肿的感觉。它是不是一种缺乏GC机制的语言为了性能的必要折衷?请问各位怎样看待它?
按投票排序
个人认为,右值引用是C++11中最细微却最重要的改动。即使不直接使用,也一样能够从中受益。同时右值引用还使更多的标准库功能成为可能,例如std::unique_ptr、std::function等。 提到栈区对象的问题,只是右值引用解决的问题之一。更全面一些的话,可以说右值引用解决的是各种情形下对象的资源所有权转移的问题。第一次码长文,代码都是临时手敲,如有错误欢迎拍砖。=====C++11之前,移动语义的缺失是C++最令人诟病的问题之一。举个栗子:问题一:如何将大象放入冰箱?这个答案是众所周知的。首先你需要有一台特殊的冰箱,这台冰箱是为了装下大象而制造的。你打开冰箱门,将大象放入冰箱,然后关上冰箱门。问题二:如何将大象从一台冰箱转移到另一台冰箱?普通解答:打开冰箱门,取出大象,关上冰箱门,打开另一台冰箱门,放进大象,关上冰箱门。2B解答:在第二个冰箱中启动量子复制系统,克隆一只完全相同的大象,然后启动高能激光将第一个冰箱内的大象气化消失。等等,这个2B解答听起来很耳熟,这不就是C++中要移动一个对象时所做的事情吗?“移动”,这是一个三岁小孩都明白的概念。将大象(资源)从一台冰箱(对象)移动到另一台冰箱,这个行为是如此自然,没有任何人会采用先复制大象,再销毁大象这样匪夷所思的方法。C++通过拷贝构造函数和拷贝赋值操作符为类设计了拷贝/复制的概念,但为了实现对资源的移动操作,调用者必须使用先复制、再析构的方式。否则,就需要自己实现移动资源的接口。为了实现移动语义,首先需要解决的问题是,如何标识对象的资源是可以被移动的呢?这种机制必须以一种最低开销的方式实现,并且对所有的类都有效。C++的设计者们注意到,大多数情况下,右值所包含的对象都是可以安全的被移动的。右值(相对应的还有左值)是从C语言设计时就有的概念,但因为其如此基础,也是一个最常被忽略的概念。不严格的来说,左值对应变量的存储位置,而右值对应变量的值本身。C++中右值可以被赋值给左值或者绑定到引用。类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。被移走资源的右值在废弃时已经成为空壳,析构的开销也会降低。右值中的数据可以被安全移走这一特性使得右值被用来表达移动语义。以同类型的右值构造对象时,需要以引用形式传入参数。右值引用顾名思义专门用来引用右值,左值引用和右值引用可以被分别重载,这样确保左值和右值分别调用到拷贝和移动的两种语义实现。对于左值,如果我们明确放弃对其资源的所有权,则可以通过std::move()来将其转为右值引用。std::move()实际上是static_cast&T&&&()的简单封装。右值引用至少可以解决以下场景中的移动语义缺失问题:按值传入参数按值传参是最符合人类思维的方式。基本的思路是,如果传入参数是为了将资源交给函数接受者,就应该按值传参。同时,按值传参可以兼容任何的cv-qualified左值、右值,是兼容性最好的方式。class People {
People(string name) // 按值传入字符串,可接收左值、右值。接收左值时为复制,接收右值时为移动
: name_(move(name)) // 显式移动构造,将传入的字符串移入成员变量
string name_;
People a("Alice"); // 移动构造name
string bn = "Bob";
People b(bn); // 拷贝构造name
构造a时,调用了一次字符串的构造函数和一次字符串的移动构造函数。如果使用const string& name接收参数,那么会有一次构造函数和一次拷贝构造,以及一次non-trivial的析构。尽管看起来很蛋疼,尽管编译器还有优化,但从语义来说按值传入参数是最优的方式。如果你要在构造函数中接收std::shared_ptr&X&并且存入类的成员(这是非常常见的),那么按值传入更是不二选择。拷贝std::shared_ptr&X&需要线程同步,相比之下移动std::shared_ptr是非常轻松愉快的。按值返回和接收输入参数一样,返回值按值返回也是最符合人类思维的方式。曾经有无数函数为了返回容器而不得不写成这样void str_split(const string& s, vector&string&* vec); // 一个按值语义定义的字符串拆分函数。这里不考虑分隔符,假定分隔符是固定的。
这样要求vec在外部被事先构造,此时尚无从得知vec的大小。即使函数内部有办法预测vec的大小,因为函数并不负责构造vec,很可能仍需要resize。对这样的函数嵌套调用更是痛苦的事情,谁用谁知道啊。有了移动语义,就可以写成这样vector&string& str_split(const string& s) {
vector&string& v;
return v; // v是左值,但优先移动,不支持移动时仍可复制。
如果函数按值返回,return语句又直接返回了一个栈上的左值对象(输入参数除外)时,标准要求优先调用移动构造函数,如果不符再调用拷贝构造函数。尽管v是左值,仍然会优先采用移动语义,返回vector&string&从此变得云淡风轻。此外,无论移动或是拷贝,可能的情况下仍然适用编译器优化,但语义不受影响。对于std::unique_ptr来说,这简直就是福音。unique_ptr&SomeObj& create_obj(/*...*/) {
unique_ptr&SomeObj& ptr(new SomeObj(/*...*/));
ptr-&foo(); // 一些可能的初始化
return ptr;
当然还有更简单的形式unique_ptr&SomeObj& create_obj(/*...*/) {
return unique_ptr&SomeObj&(new SomeObj(/*...*/));
在工厂类中,这样的语义是非常常见的。返回unique_ptr能够明确对所构造对象的所有权转移,特别的,这样的工厂类返回值可以被忽略而不会造成内存泄露。上面两种形式分别返回栈上的左值和右值,但都适用移动语义(unique_ptr不支持拷贝)。接收右值表达式没有移动语义时,以表达式的值(例为函数调用)初始化对象或者给对象赋值是这样的:vector&string& str_split(const string& s);
vector&string& v = str_split("1,2,3"); // 返回的vector用以拷贝构造对象v。为v申请堆内存,复制数据,然后析构临时对象(释放堆内存)。
vector&string& v2;
v2 = str_split("1,2,3"); // 返回的vector被复制给对象v(拷贝赋值操作符)。需要先清理v2中原有数据,将临时对象中的数据复制给v2,然后析构临时对象。
注:v的拷贝构造调用有可能被优化掉,尽管如此在语义上仍然是有一次拷贝操作。同样的代码,在支持移动语义的世界里就变得更美好了。vector&string& str_split(const string& s);
vector&string& v = str_split("1,2,3"); // 返回的vector用以移动构造对象v。v直接取走临时对象的堆上内存,无需新申请。之后临时对象成为空壳,不再拥有任何资源,析构时也无需释放堆内存。
vector&string& v2;
v2 = str_split("1,2,3"); // 返回的vector被移动给对象v(移动赋值操作符)。先释放v2原有数据,然后直接从返回值中取走数据,然后返回值被析构。
注:v的移动构造调用有可能被优化掉,尽管如此在语义上仍然是有一次移动操作。不用多说也知道上面的形式是多么常用和自然。而且这里完全没有任何对右值引用的显式使用,性能提升却默默的实现了。对象存入容器这个问题和前面的构造函数传参是类似的。不同的是这里是按两种引用分别传参。参见std::vector的push_back函数。void push_back( const T& value ); // (1)
void push_back( T&& value ); // (2)
不用多说自然是左值调用1右值调用2。如果你要往容器内放入超大对象,那么版本2自然是不2选择。vector&vector&string&& vv;
vector&string& v = {"123", "456"};
v.push_back("789"); // 临时构造的string类型右值被移动进容器v
vv.push_back(move(v)); // 显式将v移动进vv
困扰多年的难言之隐是不是一洗了之了?std::vector的增长又一个隐蔽的优化。当vector的存储容量需要增长时,通常会重新申请一块内存,并把原来的内容一个个复制过去并删除。对,复制并删除,改用移动就够了。对于像vector&string&这样的容器,如果频繁插入造成存储容量不可避免的增长时,移动语义可以带来悄无声息而且美好的优化。std::unique_ptr放入容器曾经,由于vector增长时会复制对象,像std::unique_ptr这样不可复制的对象是无法放入容器的。但实际上vector并不复制对象,而只是“移动”对象。所以随着移动语义的引入,std::unique_ptr放入std::vector成为理所当然的事情。容器中存储std::unique_ptr有太多好处。想必每个人都写过这样的代码:MyObj::MyObj() {
for (...) {
vec.push_back(new T());
MyObj::~MyObj() {
for (vector&T*&::iterator iter = vec.begin(); iter != vec.end(); ++iter) {
if (*iter) delete *iter;
繁琐暂且不说,异常安全也是大问题。使用vector&unique_ptr&T&&,完全无需显式析构,unqiue_ptr自会打理一切。完全不用写析构函数的感觉,你造吗?unique_ptr是非常轻量的封装,存储空间等价于裸指针,但安全性强了一个世纪。实际中需要共享所有权的对象(指针)是比较少的,但需要转移所有权是非常常见的情况。auto_ptr的失败就在于其转移所有权的繁琐操作。unique_ptr配合移动语义即可轻松解决所有权传递的问题。注:如果真的需要共享所有权,那么基于引用计数的shared_ptr是一个好的选择。shared_ptr同样可以移动。由于不需要线程同步,移动shared_ptr比复制更轻量。std::thread的传递thread也是一种典型的不可复制的资源,但可以通过移动来传递所有权。同样std::future std::promise std::packaged_task等等这一票多线程类都是不可复制的,也都可以用移动的方式传递。==总结移动语义绝不是语法糖,而是带来了C++的深刻革新。移动语义不仅仅是针对库作者的,任何一个程序员都有必要去了解它。尽管你可能不会去主动为自己的类实现移动语义,但却时时刻刻都在享受移动语义带来的受益。因此这绝不意味着这是一个可有可无的东西。除了移动语义,右值引用还解决了C++03中引用语法无法转发右值的问题,实现了完美转发,才使得std::function能有一个优雅的实现。相对于移动语义来说,我觉得这已经是小问题了。这部分不再展开了。
我的理解,右值引用意在解决栈区对象的所有权转移问题。对于在堆区分配的对象而言,转移对象所有权很简单,大致就是指针赋值。在C++11以前,栈区的对象只能被deep copy,而不能转移所有权,这就导致了一些确实要转移所有权而 deep copy 的不必要开销,比如函数返回临时对象给外界时会多次调用构造函数。右值引用允许一个类定义转移构造函数,比如有一个Vector类:class Vector {
Vector(int len, int initial_value) : len_(len) {
elements_ = new int[len];
memset(elements_, initial_value, len * sizeof(int));
Vector(const Vector& v) : len_(v.len_) {
elements_ = new int[len];
memcpy(elements_, v.elements_, len * sizeof(int));
int *elements_;
这时如果我们要写一个函数,返回一个在栈区分配空间的Vector对象:Vector ConstructVector(int len) {
return Vector(len, 0);
Vector v = ConstructVector(1000000);
这个函数调用的开销不小,不开编译器优化时会大概调用一次构造函数,2次deep copy。优化点在于,函数中return后面的对象是个Vector临时对象,何必对它做deep copy呢?于是在C++ 11中,我们为 Vector类定义转移构造函数:Vector(Vector &&v) : len_(len) {
elements_ = v.elements_;
这样当我们调用ConstructVector时就不会触发 deep copy了。Vector &&表示对右值的引用。转移构造函数后,通过Vector &&v传入的对象状态是未定义的。
我不同意LS的观点:“但是没有根本上的存在必要”。C++11 右值引用的定位有点像C++的模板,右值引用极大的提高了C++效率,主要应用于一些库(STL)中,实际项目开发用的少,但是并不是没有存在的必要。
就C++这个畸形种而言,R-ref以及std::move/std::swap成功地在保持代码兼容的情况下大大提高了实际程序的性能(至少就std自带库而言,因为非std自带库你要自己确保带&&的overload),可以说是一个很不错的妥协的发明了。至少就我而言我是想不到这样的创意。虽然说我对于R-ref所针对的C++设计缺陷有很多想法,然而并不能做到保持代码兼容/不违背C++一些既存原则
右值引用的本质是提供了一种系统的方法,可以在编译时区分临时对象(常常是匿名的)和具名对象,使得某些对构造对象敏感的代码可以为此优化,省略可能繁重的构造析构以及拷贝操作。这是典型的零开销原则的实例之一。你不使用没什么影响,但是使用了却可以在某些情况下极大地提升性能。这样的特性当然是多多益善啦。另,这样的特性主要是针对库作者的。
一句话答案:右值引用的出现是为了实现移动语义,顺便解决完美转发的问题,其意义在于扩充了值语义,帮助Modern C++可以全面地应用RAII。以下展开:------------------------澄清误区在具体回答右值引用的价值之前,我想先否定题主的两个提法。首先是"将这样一个问题留给程序员解决"右值引用并没有将什么“问题”留给程序员解决,而是向程序员提供更多的选择。正如Bjarne Stroustrup所说,"C++的许多设计决策根源于我对强迫人按某种特定方式行事的极度厌恶。""当我试图去取缔一个我不喜欢的语言特征时,我总抑制住这样的欲望,因为我认为我无权把个人观点强加给别人。"选择是自主权的表现,向往自由的人一定不会认为有选择是件坏事。更何况右值引用的神奇之处在于,在很多时候,使用函数库的程序员根本不用关心它的存在。例如:auto vec = init_data(args);
= make_shared&int&(11);
= resolver.resolve({"localhost", "1024"});
另一个是"一种缺乏GC机制的语言为了性能的必要折衷"由于有的存在,,因为RAII能让C++代码同时具备简洁、高性能(在某些特定的高并发环境里GC性能可能更高)和异常安全这三个特点。所以,C++暂时还不需要任何特性来作这种所谓的折衷。为什么会觉得右值引用很怪?根本原因是不理解值语义(Value Semantics)。是很多OO语言里没有的概念。在这些语言里,几乎所有的变量都是引用语义(Reference Semantics),GC掌管了所有对象的生命期管理事务, 程序员无需为此操心,只需要用变量去对象池中去引用即可。值语义虽然也有出场的机会,例如Java里的,但毕竟不是重点,所以往往被忽视。还有一个很重要的原因,在OO语言里,值语义因此也成为了C++区别于其它OO语言的特征之一。所以,在不理解值语义的情况下去谈右值引用,最多也是浅尝辄止,所以"感觉奇怪"也是很正常的,甚至会出现类似这样的问题。值语义的作用首先给出一篇作为值语义的科普,内容则不再复述,以下只讨论其意义。这篇blog的作者,曾在另一篇blog中盛赞RAII,并认为它是,也在中提到:RAII code is just naturally exception-safe with immediate cleanup, not leaking stuff until the GC gets around to finding it. This is especially important with scarce resources like file handles.但问题是,像C#的和Java的同样具有RAII的特点,但仍然有人会提出这样的问题。原因即在于C++独有的值语义:程序员通过值语义可以方便直观地控制对象生命期,让RAII用起来更自然。更何况像这段代码,vector&ifstream& produce()
vector&ifstream& ans;
for (int i = 0;
i & 10; ++i) {
ans.emplace_back(name(i));
return ans;
void consumer()
vector&ifstream& files = produce();
for (ifstream& f: files) {
用try-with-resources根本就搞不定。当然,finally还是可以搞定,只是代码会很丑。所以,值语义在C++里的作用之一就是用于控制对象生命期(),以方便通过RAII写出简洁自然、异常安全的代码。该意义非常重大,这也是右值引用在上稳坐头把交椅的原因。右值引用与值语义前面提到,值语义用于控制对象的生命期,而其具体的控制方式分为两种:生命期限于scope内:无需控制,到期自动调用析构函数。需要延长到scope外:移动语义。因为右值引用的目的在于,所以右值引用 意义即是加强了值语义对对象生命期的控制能力。在移动语义的作用下, 条款23从此作古:当你必须传回object时,不要尝试传回reference。资源管理类,如、,可以光明正大地当成int来用,而无需担心类似 条款8那样的问题:切勿创建包含auto_ptr的容器对象。右值引用与完美转发右值引用还有一个作用是实现完美转发。,但同时,这也引入了一些。个人感觉意义不如移动语义的重大,所以这里不再展开了。---------------附参考资料. Jens Weller. Herb Sutter. Andrzej Krzemieński. Howard E. Hinnant, Peter Dimov, Dave Abrahams. 陈硕
As a C++ programmer, you don't pay for what you don't need.如果你不需要rvalue ref,你可以不用,即使用了其他人的库当中有rvlue ref,对你来说基本也是透明的。没有右值语意,unique_ptr还得像原来的auto_ptr一样各种潜在错误。有了右值,你的swap生成的机器码可以仅仅使用三条机器指令就交换两个对象的内容。至于GC,有一句话是这么说的:如果有GC,那c++还剩什么。
看别人写一个非平凡的数据结构,如果不实现右值构造和operator=,我会感觉很难受。
可以看看:摘自上面的话:右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在列表上高居榜首也可以看得出来。从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。从库设计者的角度讲,它给库设计者又带来了一把利器。从库使用者的角度讲,不动一兵一卒便可以获得“免费的”效率提升…像1楼的那个搬运大象比喻,很好的描述了move和copystd::vector readFile()
std::vector retv;
… // fill retv
return retv;
这段代码低效的地方在于那个返回的临时对象。一整个vector得被拷贝一遍,仅仅是为了传递其中的一组int,当v被构造完毕之后,这个临时对象便烟消云散。这完全是公然的浪费!出现这种情况,编译器一般都会乖乖放弃优化。但对编译器来说这还不是最郁闷的一种情况,最郁闷的是:std::vector&int& v;
v = readFile(); // assignment, not copy construction
正常是这样写的void readFile(vector& v){ … // fill v }
这当然可以。但是如果遇到操作符重载呢?string operator+(string const& s1, string const& s2);
而且,就算是对于readFile,原先的返回vector的版本支持BOOST_FOREACH(int i, readFile()){
… // do sth. with i
改成引用传参后,原本优雅的形式被破坏了,为了进行以上操作不得不引入一个新的名字,这个名字的存在只是为了应付被破坏的形式,一旦foreach操作结束它短暂的生命也随之结束:vector&int& v;
readFile(v);
BOOST_FOREACH(int I, v){
// v becomes useless here
还有什么问题吗?自己去发现吧。总之,利用引用传参是一个解决方案,但其能力有限.Move语意最初的例子——完美解决方案在先前的那个例子中vector&int& v = readFile();有了move语意的话,readFile就可以简单的改成:std::vector&int& readFile()
std::vector&int& retv;
… // fill retv
return std::move(retv); // move retv out
对语意从语法上加以支持,一是有利于代码的表达,二是为优化留下空间。可以参考引用,const的作用。

我要回帖

更多关于 c 11 using 的文章

 

随机推荐