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。