您的位置:首页 > 编程语言 > C语言/C++

C++11读书笔记—4(右值系统引论,老C++的若干大坑)

2016-07-31 19:45 190 查看
说右值系统,要从 把C/C++程序员坑了无数遍的“浅拷贝深拷贝”问题说起。之后介绍C++11标准之前的大坑们。

如深拷贝浅拷贝;函数返回对象究竟是什么过程;以及函数的引用返回

一、浅拷贝与深拷贝

还是简单说一下,这个问题从C语言时代就开始了。

struct Person

{

    char* pname; //问题在这里

    int age;

    char sex;

};

如果这样拷贝时,

p1=p2;

memcpy(to,from,sizeof(Person) );

两个结构体对象元素的不同指针同时指向了一个地方。(浅拷贝)

编译器的等号只能将变量的内存空间整体拷贝,如果一个变量a是指针指向这个空间外部。拷贝后对象的变量a也指向这块外部空间。相当于两个对象同时对一个地方宣称主权。

所以这种情况要自己写函数,自己分配处理内存空间。C++类也存在同样的问题。

二、函数返回过程,又一个令人撕逼的问题

先看下面的代码:

#include<iostream>
using namespace std;
class A
{
public:
A():ap(new int(1)) { cout << "构造函数:" << ++n_cons << endl; };
A(const A & olda):ap(new int(*(olda.ap))) { cout << "copy构造函数:" << ++n_copycons << endl; };
~A()
{
//delete a;//这个暂时不写没有
cout << "析构函数:" << ++n_des << endl;
}
public:
int* ap;
static int n_cons;
static int n_copycons;
static int n_des;
};
A getA() //工厂模式常见操作
{
return A();
}
//因为坑太大,很多程序员直接不用上面这种容易理解的写法。
//羡慕java程序员吧,常用以下几种方法。
//
A* getpA()
{
A* a = new A();
return a;
}

int A::n_cons = 0;
int A::n_copycons = 0;
int A::n_des = 0;

int main()
{
{
cout << "直接赋值:" << endl;
A::n_cons = 0;
A::n_copycons = 0;
A::n_des = 0;
A a = getA();
}
{
cout << "用指针:" << endl;
A::n_cons = 0;
A::n_copycons = 0;
A::n_des = 0;
A* pa = getpA();
}

return 0;
}


停,这里会出现令人撕逼的问题答案。

下面分别是我用gcc编译的结果,与VS2015的结果。





咦?这不挺对的吗?怎么会产生撕逼呢?因为老C++标准可不是这么玩的。

因为当函数返回对象的时候。编译器会生成一个临时对象返回,如声明一个函数用来合并两个字符串:

const string strMerge (const string s1, const string s2);
大多时候是无法避免这样的临时变量产生的,但是现代编译器可以将这样的临时变量进行优化掉,这样的优化策略中,有个所谓的“返回值优化”。这是编译器替我们做的。

C++中原标准是这样的:当函数需要返回一个对象的时候,如果自己创建一个临时对象用户返回,那么这个临时对象会消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy
Constructor)以及一个析构函数(Destructor)的调用的代价。如果按这个标准做,第一个直接赋值结果应该是这样。

直接赋值:                                                                                           

构造函数:1 //函数里面的临时变量

copy构造函数:1                                                                                          

析构函数:1

//返回过程结束//下面执行a=getA();

copy构造函数:2 

析构函数:2   //这个是析构那个临时变量

析构函数:3   // 程序结束,析构a。

但是编译器实在看不过去了。编译器说:如果稍微做一点优化,就可以将成本降低到一个构造函数的代价。结果“良心编译器”替我们做了上面运行的结果。

但这么一改就坏了!!!!!!

如果没有良心编译器,因为C++标准在这里用了copy构造函数,再加上深拷贝浅拷贝的问题,我们要想直接赋值,直接返回那么做必须重载copy构造函数。。。。。那我怎么知道编译器是不是良心???于是很多程序员甘愿用第二种指针方式,冒着内存泄漏的风险也不用上一种。

三、左值与右值

(本部分参考这篇文章C++ 0x 之左值与右值、右值引用、移动语义、传导模板)

这个区分得回到原先C语言的问题中。就是有些能放等号左边,我们叫左值;有些只能放等号右边,我们叫右值。请注意,我前者没说“仅能放左边”。

书面上讲,左值具名,对应指定内存域,可访问;右值不具名,不对应内存域,不可访问。临时对像是右值。左值可处于等号左边,右值只能放在等号右边。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。

int a,b;
a = 3;
b = 4;
a = b;
b = a;
// 以下写法不合法。
3 = a;
a+b = 4;


C++有一个特色的东西,函数可以做左值。这个很重要,在c++0x中提出C++11流行的右值引用打了基础。

#include<iostream>
using namespace std;

int & getA()
{
static int a = 0; //声明,这是初始化,静态就一次。
return a;
}
int main()
{
getA() = 100;//函数居然当左值
cout << getA();
return 0;
}

那么函数的引用究竟返回什么呢?是一个对象。。。不是值

getA() = 100;在这里的意思就是a =100;
getA()放在那里,你就把他看作a(getA()函数内部return的那个东西)就行。

自此C++的右值系统导引的坑都提出来了,我们开始右值系统吧
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  读书笔记 C-C++