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

Effective C++ — 构造/析构/赋值运算(一)

2017-09-16 17:03 337 查看

Effective C++

________________________________________________________________________________________________

上一个博客我记录了Effective C++的前5个条款Effective C++ 读后积累(一)接下来是我最新总计出来的内容.

条款6:若不想使用编辑器自动生成的函数,那就应该明确拒绝

我们也知道类这个东西就是为了解决实际生活当中的实际问题的,当然我们有时候在生活中不希望有哪些事情发生,在

类中就会有不希望那些函数被使用,对吧. 举个例子,给论文加一个类,或者事情营销方案,各国机密的类你觉得应不

应该提供拷贝构造这种类呢?有些东西世界上只能存在一份. 还有我们以前学习的智能指针当中的一个Scoped_ptr,它

为了防止管理权转移的问题,他直接就禁用了拷贝构造和赋值运算符重载. 接下来的就是教大家如何让编译器不要生产

你不想使用的函数.首先有的人就想那我就不要声明就行了呗,但是如果是默认的成员函数这个时候就算你不声明,结

果编辑器还是会为你生成一个.这样行不通. 接下来又有人想到了可以将他们设置为private类型的,这样也可以. 但是

你又忽略了member函数和friend函数的存在,其实正确的做法就是声明为private但是你却不去定义他们.比如这样:

class HomeForsale
{
public:
...
private:
...
HomeForsale(const HomeForsale& a);
HomeForsale& operator=(const HomeForsale& b);
};

如果有人通过friend和member函数调用到你的隐藏函数,那么它还是会获得一个连接错误. 这种方法也是一个极骚的方法.

所以有人想拷贝你的对象编译器阻拦他,它要是还想用friend和member函数调用,连接器又去阻挠它. 

第二种方法:

class Uncopyable{
protected:
Uncopyable()
{}
~Uncopyable()
{}
private:
Uncopyable(const Uncopyable& a);
Uncopyable& operator=(const Uncopyable& b);
};

class HomeForSale :private Uncopyable
{
.......
};

有人说这是在干啥? 那么请你再认真仔细的看. 任何人哪怕他是调用了member和friend函数尝试拷贝HomeForsale对象,编译器便

尝试生成一个copy构造函数和copy assignment操作符,这些函数的"编译器生成版会尝试去调用其bass class 对于的

兄弟",但那些调用被拒绝了. 因为其base class拷贝函数为private.

这两种方法对于你来说都是可行的,合理运用即可.

条款7:为多态基类声明为virtual析构函数

这里的问题其实不是很难思考的,举个例子大家就明白了,看如下代码:

class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}

virtual void func2()
{
cout << "Base::func2" << endl;
}

virtual ~Base()
{
cout << "~Base" << endl;
}

private:
int a;
};

class Derive :public Base
{
public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}
virtual ~Derive()
{
cout << "~Derive"<< endl;
}
private:
int b;
};

void Test1()
{
Base* q = new Derive;
delete q;
}
int main()
{
Test1();
system("pause");
return 0;
}


view
plai 
注意这里我先让父类的析构函数不为虚函数(去掉virtual),我们看看输出结果:



这里它没有调用子类的析构函数,因为他是一个父类类型指针,所以它只能调用父类的析构函数,无权访问子类的析构函

数,这种调用方法会导致内存泄漏,所以这里就是有缺陷的,但是C++是不会允许自己有缺陷,他就会想办法解决这个问

题,现在我们让加上为父类析构函数加上virtual,让它变回虚函数,我们再运行一次程序的:



诶! 子类的虚函数又被调用了,这里发生了什么呢??  来我们老方法打开监视窗口。



这种情况对于我们来说是一个引起灾难的秘诀我跟你讲~  就是内存泄露. 所以当有多态情况出现的时候,你就赶紧把

基类析构函数定义为virtual函数. 是这样的,我身边有一个朋友也知道这个规则,他无论写什么程序都往析构函数前

面加virtual,这样我就不会了,不管程序内部有没有虚函数的存在,有没有多态的存在,他都加virtua. 他完全就是

在搞事情. 

总结:

带有多态性质的base classer应该声明一个virtual析构函数.

如果class带有任何virtual函数,他就应该拥有一个virtual析构函数.

条款8:别让异常逃离析构函数 ???

总结

析构函数绝对不要吐出异常.如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常

,然后吞下他们或结束程序.

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作(函数不在

析构函数内)

条款09:绝对不在构造和析构过程中调用virtual函数.

你不该在构造函数和析构函数期间调用virtual函数,因为这样的调用不会给你带来预想的结果.举个例子:

class Transaction{      //base class
public:
Transaction();
virtual void logTransaction() const = 0;

...
};

Transaction::Transaction() //base class
{
{
...
logTransaction();
}

class BuyTransaction :public Transaction //derived class
{
public:
virtual void logTransaction() const;
};

class SellTransaction : public Transaction //derived class
{
public:
virtual void logTransaction() const;
...
};


现在,当以下这行被执行,会发生什么事:

buyTransaction b;

无疑地会有一个BuyTransaction构造函数被调用,但首先Transaction构造函数一定会更早的调用; derived class对

象内的base class成分会在derived class自身成分被构造出来之前先构造妥当.Transaction构造函数的最后一行调用

logTransaction函数,这里正是引起惊奇的起点.这时候被调用的logTransaction是Transaction内的版本,不是

BuyTransaction内的版本.即使目前即将建立的对象类型是BuyTransaction.是的,base class构造期间 virtual函数

绝不会下降到derived classes阶层. 取而代之的是,对象的作为就像隶属base类型一样.非正式的说法或许比较传神:

在base class构造期间,virtual函数不是virtual函数.

由于base class构造函数的执行更早于derived class构造函数,当base class构造函数执行时derived class的成

员变量尚未初始化.如果此期间调用的virtual函数下降至derived class阶层,要知道derived class的函数几乎必

然取用local成员变量,而那些成员变量尚未初始化,这将是一张通向不明确行为和彻夜调试大会串的直达车票. 

"要求使用对象内部尚未初始化部分" 是一个危险代名词,所以C++不让你走这条路.

在derived class对象的base class构造期间,对象类型时base class而不是derived class。不只virtual函数会被编

辑器解析至base class。 若使用运行期类型信息,也会把对象视为base class类型. 相同的道理也适合析构函数. 一

旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视他们仿佛不再存在.

进入base class析构函数后对象就成了一个base class对象,而C++的任何部分包括virtual函数,dynami_casts等等也

这样看待它.

条款10: 令operator= 返回一个reference to *this 

这个条款大家应该很容易就明白的,关于赋值,有趣的是你可以把他们写成连锁反应.

int x = 0;

int y = 0;

int z = 0;

x = y = z = 15;

同样有趣的是,赋值采用右结合律,所以上述连锁赋值被解析为:

x = (y = (z = 15));

这里15先被赋值给z,然而其结果再被赋值给y,然后其结果再被赋值给x. 

为了实现"连锁赋值",赋值操作符必须返回一个reference指向操作符的左侧实参. 对 就这么简单就完了....

总结:

令赋值操作符返回一个reference to *this。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: