begin()(和end())可以被做为x.begin()的成员或一个独立的函数被称为开始(x)。
成员版本优先。
参考:
∙theC++draftsection6.5.4(note:
changednottouseconcepts)
∙[N2243==07-0103]ThorstenOttosen:
∙[N3257=11-0027]JonathanWakelyandBjarneStroustrup:
(Option5waschosen).
初始化列表
推导
1
2
3
4
5
6
7
8
9
vectorv={1,2,3.456,99.99};
list>languages={
{"Nygaard","Simula"},{"Richards","BCPL"},{"Ritchie","C"}
};
map,vector>years={
{{"Maurice","Vincent","Wilkes"},{1913,1945,1951,1967,2000}},
{{"Martin","Ritchards"},{1982,2003,2007}},
{{"David","John","Wheeler"},{1927,1947,1951,2004}}
};
初始化列表不再只针对于数组了。
定义一个接受{}初始化列表的函数(通常是初始化函数)接受一个std:
:
initializer_list的参数类型,例如:
1
2
3
4
5
6
voidf(initializer_list);
f({1,2});
f({23,345,4567,56789});
f({}); //theemptylist
f{1,2};//error:
functioncall()missing
years.insert({{"Bjarne","Stroustrup"},{1950,1975,1985}});
初始化器列表可以是任意长度的,但必须同种类型的(所有元素必须的模板参数类型,T,或可转换T)。
一个容器可能实现一个初始化列表构造函数如下:
1
2
3
4
5
6
7
8
9
10
templateclassvector{
public:
vector(std:
:
initializer_lists)//initializer-listconstructor
{
reserve(s.size()); //gettherightamountofspace
uninitialized_copy(s.begin(),s.end(),elem); //initializeelements(inelem[0:
s.size()))
sz=s.size(); //setvectorsize
}
//...asbefore...
};
直接初始化和复制初始化的区别是对初始化列表的维护,但是因为初始化列表的相关联的频率就降低了。
例如std:
:
vector有一个int类型显示构造函数和initializer_list构造函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
vectorv1(7); //ok:
v1has7elements
v1=9; //error:
noconversionfrominttovector
vectorv2=9; //error:
noconversionfrominttovector
voidf(constvector&);
f(9); //error:
noconversionfrominttovector
vectorv1{7}; //ok:
v1has1element(withitsvalue7.0)
v1={9}; //okv1nowhas1element(withitsvalue9.0)
vectorv2={9}; //ok:
v2has1element(withitsvalue9.0)
f({9}); //ok:
fiscalledwiththelist{9}
vector>vs={
vector(10), //ok:
explicitconstruction(10elements)
vector{10}, //okexplicitconstruction(1elementwiththevalue10.0)
10 //error:
vector'sconstructorisexplicit
};
函数可以作为一个不可变的序列访问initializer_list。
例如:
1
2
3
4
voidf(initializer_listargs)
{
for(autop=args.begin();p!
=args.end();++p)cout<<*p<<"\n";
}
仅具有一个std:
:
initializer_list的单参数构造函数被称为初始化列表构造函数。
标准库容器,string类型及正则表达式均具有初始化列表构造函数,以及(初始化列表)赋值函数等。
一个初始化列表可被用作Range,例如,表达式Range。
初始化列表是一致泛化初始化解决方案的一部分。
他们还防止类型收窄。
一般来说,你应该通常更喜欢使用{}来代替()初始化,除非你想用c++98编译器来分享代码或(很少)需要使用()调用没initializer_list重载构造函数。
参考:
∙theC++draft8.5.4List-initialization[dcl.init.list]
∙[N1890=05-0150]BjarneStroustrupandGabrielDosReis:
(anoverviewofinitialization-relatedproblemswithsuggestedsolutions).
∙[N1919=05-0179]BjarneStroustrupandGabrielDosReis:
∙[N2215=07-0075]BjarneStroustrupandGabrielDosReis:
∙[N2640=08-0150]JasonMerrillandDaveedVandevoorde:
(finalproposal).
统一初始化语法和语义
c++98提供了几种方法初始化一个对象根据其类型和初始环境。
滥用时,会产生可以令人惊讶的错误和模糊的错误消息。
推导:
1
2
3
4
stringa[]={"foo","bar"}; //ok:
initializearrayvariable
vectorv={"foo","bar"}; //error:
initializerlistfornon-aggregatevector
voidf(stringa[]);
f({"foo","bar"}); //syntaxerror:
blockasargument
和
1
2
3
4
inta=2; //"assignmentstyle"
intaa[]={2,3}; //assignmentstylewithlist
complexz(1,2); //"functionalstyle"initialization
x=Ptr(y); //"functionalstyle"forconversion/cast/construction
和
1
2
3
inta
(1); //variabledefinition
intb(); //functiondeclaration
intb(foo);//variabledefinitionorfunctiondeclaration
要记得初始化的规则并选择最好的方法去初始化是比较难的。
C++11的解决方法是允许所有的初始化使用初始化列表
1
2
3
4
5
6
7
8
9
10
11
Xx1=X{1,2};
Xx2={1,2}; //the=isoptional
Xx3{1,2};
X*p=newX{1,2};
structD:
X{
D(intx,inty):
X{x,y}{/*...*/};
};
structS{
inta[3];
S(intx,inty,intz):
a{x,y,z}{/*...*/};//solutiontooldproblem
};
重点是,x{a}在在执行代码中都创建了一个相同的值,所以在使用“{}”进行初始化合法的情况下都产生了相同的结果。
例如:
1
2
3
4
5
Xx{a};
X*p=newX{a};
z=X{a}; //useascast
f({a}); //functionargument(oftypeX)
return{a}; //functionreturnvalue(functionreturningX)
参考:
∙theC++draftsection?
?
?
∙[N2215==07-0075]BjarneStroustrupandGabrielDosReis:
∙[N2640==08-0150]JasonMerrillandDaveedVandevoorde:
(finalproposal).
右值引用和移动语义
左值(用在复制操作符左边)和右值(用在复制操作符右边)的区别可以追溯到ChristopherStrachey (C++遥远的祖先语言CPL和外延语义之父)的时代。
在C++中,非const引用可以绑定到左值,const引用既可以绑定到左值也可以绑定要右值。
但是右值却不可以被非const绑定。
这是为了防止人们改变那些被赋予新值之前就被销毁的临时变量。
例如:
1
2
3
4
voidincr(int&a){++a;}
inti=0;
incr(i); //ibecomes1
incr(0); //error:
0innotanlvalue
如果incr(0)被允许,那么就会产生一个无法被人看到的临时变量被执行增加操作,或者更糟的0会变成1.后者听起来很傻,但实际上确实存在这样一个bug在Fortran编译器中:
为值为0的内存位置分配。
到目前为止还好,但考虑以下代码:
1
2
3
4
5
6
templateswap(T&a,T&b) //"oldstyleswap"
{
Ttmp(a); //nowwehavetwocopiesofa
a=b; //nowwehavetwocopiesofb
b=tmp; //nowwehavetwocopiesoftmp(akaa)
}
如果T是一个复制元素要付出昂贵代价的类型,比如string和vector,swap将会变成一个十分昂贵的操作(对于标准库来说,我们有专门化的string和vector来处理)。
注意一下这些奇怪的现象:
我们并不想任何变量拷贝。
我们仅仅是想移动变量a,b和tmp的值。
在C++11中,我们可以定义“移动构造函数”和“移动赋值操作符”来移动,而不是复制他们的参数:
1
2
3
4
5
6
7
8
templateclassvector{
//...
vector(constvector&); //copyconstructor
vector(vector&&); //moveconstructor
vector&operator=(constvector&); //copyassignment
vector&operator=(vector&&); //moveassignment
}; //note:
moveconstructorandmoveassignmenttakesnon-const&&
//theycan,andusuallydo,writetotheirargument
&&表明“右值引用”。
一个右值引用可以绑定到一个右值(而不是一个左值):
1
2
3
4
5
6
Xa;
Xf();
X&r1=a; //bindr1toa(anlvalue)
X&r2=f(); //error:
f()isanrvalue;can'tbind
X&&rr1=f(); //fine:
bindrr1totemporary
X&&rr2=a; //error:
bindaisanlvalue
赋值这个操作的背后思想,并不是拷贝,它只是构造一个源对象的代表然后再替换。
例如,strings1=s2的移动,它不是产生s2的拷贝,而是让s1把s2中字符变为自己的同时删除自己原有的字符串(也可以放在s2中,但是它也面临着被销毁)
我们如何知道是否可以简单的从源对象进行移动?
我们可以告诉编译器:
1
2
3
4
5
6
7
template
voidswap(T&a,T&b) //"perfectswap"(almost)
{
Ttmp=move(a); //couldinvalidatea
a=move(b); //couldinvalidateb
b=move(tmp); //couldinvalidatetmp
}
move(x)只是意味着你“你可以把x当作一个右值”,
如果把move()称做eval()也许会更好,但是现在move()已经用了好多年了。
在c++11中,move()模板(参考简介)和右值引用都可以使用。
右值引用也可以用来提供完美的转发。
在C++0x的标准库中,所有的容器都提供了移动构造函数和移动赋值操作符,那些插入新元素的操作,如insert()和push_back(),也都有了可以接受右值引用的版本。
最终结果是,在无用户干预时,标准容器和算法的性能都提升了,因为复制操作的减少。
参考:
∙N1385