Effective c++ 读书总结 6 - 10
条款六:若不想使用编译器自动生成的函数、就该明确拒绝
当一个类不需要自动生成的函数( 构造、拷贝构造、拷贝运算符、析构)时、有以下两种方式解决:
1.主动将函数声明为private
2.继承一个有限制的base类:derived类自动生成的函数会企图调用base中对应的函数、这时就会编译出错。
条款七:为多态基类声明virtual析构函数
1.没声明virtual析构函数导致的内存泄漏
当Base* b = new Derived();父类指针指向一个子类实例时、如果base没有将析构函数声明为virtual、delete b;释放时只会调用父类的析构函数、导致对象的子类部分没有被删除、而内存泄漏。
2.如果有一个virtual成员函数、那么就应该声明virtual析构函数
如果一个类有virtual函数、那么也就是说这个类希望成为一个父类、那么为了避免内存泄漏、需要声明virtual析构函数、另外如果一个类没有virtual成员、就意味着它不希望成为父类、所以也不要声明virtual析构函数、因为那会浪费空间(一个指向virtual table的指针)、在我们自定义类时如果不希望它成为父类、使用关键字final(之前没有、新版本有了)
3.virtual table
对于子类继承父类并且重写virtual函数这样情况、对象就必须携带某些信息(因为要在运行期确定具体调用的是哪个函数、父类的还是子类的)、有一个指向virtual table的一个指针、virtual table中都是指向调用函数的函数指针、32位计算机每个指针4个字节64位就是8个字节。指针vptr。
4.希望有一个抽象类、但是没有pure virtual函数、可将析构声明为pure virtual
virtual ~Test() = 0;抽象类不能实例化。因为在释放子类对象时调用完子类的析构函数还要调用父类的析构、那么这个pure virtual析构需要一份实现、不然则会发生链接期错误。
条款八:别让异常逃离析构函数
1、析构函数抛出异常可能会造成内存泄漏
析构函数中要做的工作通常是释放内存资源、但是如果在释放资源时抛出异常、那么后面的对象就不能释放、造成泄漏、像:
class Test
{
private:
vector<NODE*> nodes;
public:
~Test()
{
for(auto node : nodes)
{
delete node; //如果在删除时抛出异常、那么数组中后面的元素都不会被释放了
node = NULL;
}
}
};
2、析构函数抛出异常的两种处理方式
第一种方式就是结束程序:如果程序遭遇一个析构期间发生的错误、且后续无法继续执行、那么结束程序是一个合理的选项。
Test::~Test()
{
try{
close(); //捕捉close函数抛出的异常
}catch(Exception e)
{
abort(); //直接结束程序
}
}
第二种方式是析构函数吞下异常( 当不影响后续程序执行时可以采用 )
3、提供一个释放资源的接口
很多时候为了防止忘记释放资源、释放资源的操作都在析构函数中。但是一旦析构函数抛出异常就只能使用上述说明的两种方式来处理、这时调用者完全不知情、并且他没有权利对异常进行自定义的处理、所以我们可以将释放资源的操作写在一个release函数中、然后让析构函数也调用这个函数、这样用户可以自定义处理异常、也可以忽略( 使用析构释放 )、这样做是给用户提供了处理异常的机会。
class Test
{
private:
vector<NODE*> nodes;
bool releaseflag;
public:
~Test()
{
try{
release();
}catch(Exception e){
abort();
}
}
void release()
{
if(releaseflag == false){
for(auto node : nodes){
delete node;
node = NULL;
}
releaseflag = true;
}
}
};
条款九:绝不在构造和析构过程中调用virtual函数
1、子类对象没有构造完成就调用virtual函数、可能产生不想要的结果
class Test
{
public:
Test(){
print();
}
public:
virtual void print();
};
class TT : public Test
{};
int main()
{
TT t; //在父类构造期间、调用virtual函数。此时子类部分还没有构造、当前的类型型别是Test、
//所以调用的是父类的print函数、如果父类没有实现、则链接期错误、如果父类实现了、
//就调用父类的、但是类的设计其实是想根据不同的子类实例执行不同的print函数、这里做不到
return 0;
}
正确的方式可以
class Test
{
private:
void print(int n){
cout<<n<<endl;
}
public:
Test(int n){
print(n);
}
};
class A : public Test
{
public:
A():Test(0){
//使用参数将子类信息传递到父类、执行统一的操作
}
};
3、要注意构造和析构调用的成员函数中可能包含virtual函数(避免)
条款十:令operator=返回一个reference to *this
1、为了实现连锁赋值、赋值操作符必须返回一个左值(reference to *this)
class String
{
private:
char buf[SIZE];
public:
String& operator=(const String& str)
{
strcpy(buf,str.buf);
return *this;
}
}
上一篇: 差分(1.0版)
推荐阅读
-
《Effective C++》读书笔记 资源管理
-
《Effective C++》读书笔记 被你忽略的关于构造析构赋值
-
Effective C++ 读书笔记 Item26 为什么要推迟变量的定义?
-
《Effective C++》读书笔记
-
C++程序员应了解的那些事(36)Effective STL第6条:当心C++编译器中最烦人的分析机制 --- 调用构造函数被误认为是函数声明的问题
-
《Effective C++》条款04总结
-
effective c++条款总结(5)
-
《Effective C++》学习总结(条款06 - 10)
-
Effective C++ 条款5、6、7
-
Effective C++ 条款总结