1,了解C++编译器默认编写的函数
对于一个类,编译器会根据需要创建默认构造函数,默认拷贝函数,默认析构函数以及默认重载赋值运算符。但值得注意的是,只有这些函数需要被调用并且我们没有手动定义的时候,它们才会被编译器创建。
值得注意的是,对于拷贝构造函数或者赋值运算符的重载,由于默认的只是简单的赋值,所以对于成员变量为const的情况下,编译器也不会生成对应的默认函数。甚至在我测试的C++14中,如果存在const成员变量,编译器会拒绝生成默认构造函数。(不一定正确)
2,如果不想使用编译器自动生成的函数,应该明确拒绝。
书上的例子并不是很好懂,不过我们可以用delete来组织默认拷贝函数和赋值运算符的实现。
3,使用多态的时候,尽量将基类的析构函数声明为虚析构。
析构的时候,先调用派生类的析构函数,再调用基类的析构函数(和构造的时候相反)。
如果不声明为析构,我们将只会调用基类的析构函数,导致派生类的部分内存没有完全释放,引起内存泄露的问题。
但是并非所有的派生类都需要声明虚析构,因为存在虚函数的类会维护一个虚表指针,这会造成额外的开销。通常情况下,我们使用基类指针指向派生类且派生类的时候,需要将析构函数声明为虚函数。
为了避免有时候忘记声明为虚函数,我们可以把基类的析构函数声明为纯虚函数,这样也可以调用派生类的析构函数,大概是这样写的:
class A
{
public:
A()
{
std::cout << __func__ << std::endl;
}
virtual ~A() = 0;
};
A::~A()
{
std::cout << __func__ << std::endl;
}
class B :public A
{
public:
B()
{
std::cout << __func__ << std::endl;
}
~B() {
std::cout << __func__ << std::endl;
}
};
值得注意的是,此时A依旧为纯虚基类,不能单独实现。此外,我们尽管用了virtual ~A() = 0,但依旧要为这个函数添加定义,这是因为我们在析构派生类的时候,仍然需要调用A的析构函数。
4,不要让异常逃离析构函数。
5,不要在构造函数和析构函数中调用虚函数。
https://www.cnblogs.com/chio/archive/2007/09/09/887598.html
基类的构造发生在派生类之前,如果此时基类构造函数中调用了虚函数,此时虚表指针还没有初始化,我们将无法使用多态,调用的虚函数仍然会执行基类的函数,这样也可以避免调用了未初始化的派生类成员变量,但总归是不安全的,所以要尽量避免。
析构函数类似,基类的析构函数调用的时候,派生类的内存已经被释放,此时也无法使用多态。
class A
{
public:
A() { show(); }//行4
~A()
{
show1();
}
virtual void show() { std::cout <<"A"<<std::endl; }
virtual void show1() { std::cout << "delete A" << std::endl; }
};
class B :public A
{
public:
B() { show(); }
~B()
{
show1();
}
virtual void show() { std::cout << "B"<<std::endl; }
virtual void show1() { std::cout << "delete B" << std::endl; }
};
结果为:
A
B
delete B
delete A
Hello World!
6,赋值操作符的重载时要处理自我赋值。
具体做法是这样:
A(const A& _a)
{
if (&_a == this)
return;
.....
}
如果传入的是自身,那就什么都不做。
此外,赋值运算符重载或者拷贝构造函数一定要考虑好所有成员变量的拷贝问题,有时候因为继承或者含有别的类的成员变量导致事情变得复杂,需要认真考虑。