C++11标准提高了C++语言编程效率,如定义了右值引用、移动构造等。效率就是语言的生命,因此,提高编程效率才是对语言改进的目标。本节将针对C++11标准中能够提高编程效率的常用部分进行讲解。
一 右值引用
在C++11标准出现之前,程序中只有左值与右值的概念,简单来说,左值就是“=”符号左边的值,右值就是“=”符号右边的值。区分左值与右值的一个简单、有效的方法为:可以取地址的是左值,不可以取地址的是右值。
C++11标准对右值进行了更详细的划分,将右值分为纯右值与将亡值。纯右值是指字面常量、运算表达式、lambda表达式等;将亡值是那些即将被销毁却可以移动的值,如函数返回值。随着对右值的详细划分,C++11标准提出了右值引用的概念,右值引用就是定义一个标识符引用右值,右值引用通过“&&”符号定义,定义格式如下所示:
类型&& 引用名称=右值; 在上述格式中,类型是要引用的右值的数据类型,“&&”符号表明这是一个右值引用,引用名称遵守标识符命名规范,“=”符号后面是要引用的右值。下面定义一些右值引用,示例代码如下所示:
int x = 10, y = 20;
int&& r1 = 100; //字面常量100是一个右值
int&& r2 = x + y; //表达式x+y是一个右值
int&& r3 = sqrt(9.0); //函数返回值是一个右值与左值引用相同,右值引用在定义时也必须初始化。右值引用只能引用右值,不能引用左值,错误示例代码如下所示:
int x = 10, y = 20;
int&& a = x; //错误
int&& b = y; //错误在上述代码中,变量x、y都是左值,因此不能将它们绑定到右值引用。需要注意的是,一个已经定义的右值引用是一个左值,即已经定义的右值引用是可以被赋值的变量,因此不能使用右值引用来引用另一个右值引用,示例代码如下所示:
int&& m = 100;
int&& n = m; //错误,m是变量,是左值,不能被绑定到右值引用n上二 移动构造
C++11标准提出右值引用主要的目的就是在函数调用中解决将亡值(临时对象)带来的效率问题。关于这个东西,测试代码没有得到书本上的效果,而且得到的效果,根我原来的认知也相差很远!!!这大概是编译器得到进步吧。
测试代码1:
#include <list>
#include <algorithm>
#include <memory>
static int counter = 0;
using namespace std;
class myclass{
public:
int num;
myclass(){
num = ++counter;
cout << "myclass init" << endl;
}
myclass(myclass &m){
cout << num << ":myclass copy" << endl;
m.zhe();
}
myclass(const myclass &m){
cout << "myclass const copy" << endl;
m.zhe();
}
myclass& operator=(myclass &m){
cout << "myclass operator=" << endl;
return m;
}
~myclass(){
cout << num << ":myclass delete" << endl;
}
void show(){
cout << "你瞅啥" << endl;
}
void zhe(){}
void zhe() const{}
};
myclass hehe()
{
myclass m;
m.zhe();
m.show();
return m;
}
int main()
{
myclass m = hehe();
m.zhe();
m.show();
cout << "--endl--" <<endl;
return 0;
}测试结果:
myclass init
你瞅啥
你瞅啥
--endl--
1:myclass delete原书上是想通过类似这样的例程,引出拷贝构造函数被调用的问题,但是我的编译器没有调用拷贝构造函数。我记忆中这时候构造函数也应该是被调用的才对,我用的QT,应该是QT的优化导致的。 猜测是QT5编译器(遵守C++11标准)对程序进行了优化,减少了临时对象的生成。
测试代码2:
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <algorithm>
#include <memory>
using namespace std;
class myclass{
public:
myclass(){
cout << "myclass init" << endl;
}
myclass(myclass &m){
cout << "myclass copy" << endl;
m.zhe();
}
myclass(const myclass &m){
cout << "myclass const copy" << endl;
m.zhe();
}
myclass& operator=(myclass &m){
cout << "myclass operator=" << endl;
return m;
}
~myclass(){
cout << "myclass delete" << endl;
}
void show(){
cout << "你瞅啥" << endl;
}
void zhe(){}
void zhe() const{}
};
int main()
{
cout << "测试" << 1 << endl;
myclass m1;
cout << "测试" << 2 << endl;
myclass m2(m1);
cout << "测试" << 3 << endl;
myclass m3 = m1;
cout << "测试" << 4 << endl;
myclass m4;
cout << "测试" << 5 << endl;
m4 = m3;
cout << "测试" << 6 << endl;
m1.show();
m2.show();
m3.show();
m4.show();
cout << "--endl--" <<endl;
return 0;
}
测试结果:
测试1
myclass init
测试2
myclass copy
测试3
myclass copy
测试4
myclass init
测试5
myclass operator=
测试6
你瞅啥
你瞅啥
你瞅啥
你瞅啥
--endl--
myclass delete
myclass delete
myclass delete
myclass delete
测试代码3:
这是在测啥?全体构造函数开大会吗?还是重载函数开大会,意图是让它调用拷贝构造函数,结果就不低头啊。一定要myclass m2(m1);和 myclass m3 = m1;诡异的是myclass m3 = m1;怎么不调用重载=的函数呢?咱重载的=不香吗?
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <algorithm>
#include <memory>
using namespace std;
class myclass{
public:
int num;
int *p;
myclass(int n);
myclass(myclass &m);
myclass(myclass &&m);
myclass(const myclass &m);
myclass(const myclass &&m);
myclass operator=(myclass m);
myclass& operator=(myclass &m);
myclass& operator=(const myclass &m);
~myclass(){
delete p;
cout << num << ":myclass delete" << endl;
}
void show(){
cout << num << ":你瞅啥?" << endl;
}
void zhe(){}
void zhe() const{}
};
myclass::myclass(int n):num(n){
p = new int[n];
cout << "myclass init" << endl;
}
myclass::myclass(myclass &m){
cout << num << ":myclass(myclass &m)" << endl;
m.zhe();
}
myclass::myclass(myclass &&m){
cout << num << "意图是演示移动构造函数" << endl;
m.zhe();
}
myclass::myclass(const myclass &&m){
cout << num << "const意图是演示移动构造函数" << endl;
m.zhe();
}
myclass::myclass(const myclass &m){
cout << "myclass const copy" << endl;
m.zhe();
}
myclass myclass::operator=(myclass m){
cout << "myclass operator=" << endl;
return m;
}
myclass& myclass::operator=(myclass &m){
cout << "myclass operator=" << endl;
return m;
}
myclass& myclass::operator=(const myclass &m){
cout << "myclass operator=" << endl;
return *this;
}
myclass hehe(int num = 10086)
{
myclass m(num);
m.zhe();
m.show();
return m;
}
int main()
{
cout << "测试1" << endl;
myclass&& m1 = hehe();
m1.zhe();
m1.show();
cout << "测试2" << endl;
myclass m2 = hehe(10010);
m2.zhe();
m2.show();
cout << "--endl--" <<endl;
return 0;
}
执行结果:
测试1
myclass init
10086:你瞅啥?
10086:你瞅啥?
测试2
myclass init
10010:你瞅啥?
10010:你瞅啥?
--endl--
10010:myclass delete
10086:myclass delete三 move()函数
移动构造函数是通过右值引用实现的,对于左值,也可以将其转化为右值,实现程序的性能优化。C++11在标准库utility中提供了move()函数,该函数的功能就是将一个左值强制转换为右值,以便可以通过右值引用使用该值。move()函数的用法示例代码如下所示:
int x = 10; int&& r = move(x); //将左值x强制转换为右值在上述代码中,move()函数将左值x强制转换为右值,赋值给右值引用r。如果类中有指针或者动态数组成员,在对象被拷贝或赋值时,可以直接调用m ove()函数将对象转换为右值,去初始化另一个对象。使用右值进行初始化,调用的是移动构造函数,而不是拷贝构造函数,这样就可以避免大量数据的拷贝,能够极大地提高程序的运行效率。
A a(100); A b(a); //对象a是左值,调用拷贝构造函数但是,如果将对象a转换为右值,则会调用移动构造函数,示例代码如下所示:
A a(100); A c(move(a)); //对象a被转换为右值,调用移动构造函数当对象内部有较大的堆内存数据时,应当定义移动构造函数,并使用m ove()函数完成对象之间的初始化,以避免没有意义的深拷贝。
四 完美转发
一个已经定义的右值引用其实是一个左值,这样在参数转发(传递)时就会产生一些问题。例如,在函数的嵌套调用时,外层函数接收一个右值作为参数,但外层函数将参数转发给内层函数时,参数就变成了一个左值,并不是它原来的类型了。下面通过案例演示参数转发的问题
测试代码:
#include <iostream>
using namespace std;
template <class T>
T add(T &t1,T &t2)
{
cout << "我只接收左值" << endl;
t1 = 100 + t1;
t2 = 200 + t2;
return t1 + t2;
}
template <class T>
T add(T &&t1,T &&t2)
{
cout << "我只接收右值" << endl;
t1 = 300 + t1;
t2 = 300 + t2;
return t1 + t2;
}
int main()
{
int a = 5;
int b = 10;
cout << add<int>(a,b) << endl;
cout << add<int>(11,13) << endl;
int c = 5;
int d = 6;
cout << add<int>(move(c),move(d)) << endl;
cout << "--endl--" <<endl;
return 0;
}
测试结果:
我只接收左值
315
我只接收右值
624
我只接收右值
611
--endl--C++11标准提供了一个函数forward(),它能够完全依照模板的参数类型,将参数传递给函数模板中调用的函数,即参数在转发过程中,参数类型一直保持不变,这种转发方式称为完美转发。
transimit(forward<U>(u)); //调用forward()函数实现完美转发测试代码:
#include <iostream>
using namespace std;
class Base
{
public:
int _n;
double _d;
Base(){
_n = 0;
cout << _n << ":Base init 1" << endl;
}
Base(double d):_d(d){
_n = 10086;
cout << _n << ":"<< _d << ":Base init 3" << endl;
}
Base(int n):_n(n){
cout << _n << ":Base init 2" << endl;
}
~Base(){
cout << _n << ":Base delete 2" << endl;
}
};
class Child:public Base
{
public:
using Base::Base;
Child(){
_n = 0;
cout << _n << ":Child init 1" << endl;
}
~Child(){
cout << _n << ":Child delete 2" << endl;
}
void show(){
cout << __func__<< ":_n = " << _n << endl ;
}
void show(double&&){
cout << __func__<< ":_n = " << _n << endl ;
cout << __func__<< ":_d = " << _d << endl ;
}
};
int main()
{
Child c1;
c1.show();
Child c2(5);
c2.show();
Child c3(5.25);
c3.show(forward<double>(0.1));
cout << "--endl--" <<endl;
return 0;
}
测试结果:
0:Base init 1
0:Child init 1
show:_n = 0
5:Base init 2
show:_n = 5
10086:5.25:Base init 3
show:_n = 10086
show:_d = 5.25
--endl--
10086:Child delete 2
10086:Base delete 2
5:Child delete 2
5:Base delete 2
0:Child delete 2
0:Base delete 2