条款5:c++默认编写构造,析构,copy构造,copy assignment函数
- 对于copy构造,和copy assignment函数,c++默认情况下会自动编写。
- 但是有些特殊情况下是不会自动声明copy assignment函数(这些情况copy构造依然自动声明),而是将其设置为=delete,此时必须显示编写自己copy assignment函数:
1、成员变量中含有non-static reference成员(为什么类的引用成员在默认copy assignment中不能赋值?)(成员指针不会发生这种情况)或者含有const 成员。
2、成员变量中含有non-static的不能使用copy assignment的成员。
3、继承了不能使用copy assignment的基类。 - 只有当调用析构函数、copy构造函数、copy assignment运算符时,编译器才生成它们,请大家分清楚,声明和生成是两码事。
条款6:不让编译器自动生成默认函数
- 将自动生成的函数声明为private,而且只声明他们,而不定义他们(声明语句后面连花括号都不加)。或者继承一个将函数声明为private的基类。
- 不过如果基类的copy构造和copy赋值函数不可用(参考上一条),那么他的派生类的copy构造和copy赋值函数也不会自动生成,因为派生类的会调用基类的copy构造和copy赋值函数。
- 当然可以显示声明这些函数为=delete。
条款7:virtual析构函数
- 当一个类作为多态性(基指针指向派生类对象)基类的时候,需要将其析构函数设置为virtual;否则,通常情况下不会设置为virtual(除非该类中有其他virtual函数)。一个类的某个函数设置为virtual是需要付出额外的代价的:类需要多存储一个指针vptr来指向由函数指针构成的数组vtbl。
- 不要随意继承标准库的类,因为很多没有virtual函数,这样很容易造成内存泄漏。
- 如果想将某个没有pure virtual函数的类声明为抽象类,那就将析构函数声明为pure virtual函数,并且定义这个析构函数。
条款9:不要在构造和析构函数内使用virtual函数
- 在基类构造或者析构函数内使用virtual函数,会导致virtual函数不会调用派生类的virtual函数。因为在创建派生类对象时,先调用基类构造函数构造基类对象,而此时编译器会认为该对象是基类类型,此时如果用typeid和dynamic_cast都会认为是基类类型,此时派生类是不可见的。
条款11:在operator=中处理“自我赋值”
- 先copy一个备份,然后删除原来那个值,然后将备份赋值给新值。
条款17:以独立的语句将new对象置入智能指针
std::shared_ptr<Object> p = std::shared_ptr<Object>(new Object);
method(p, method2()); //1
method( std::shared_ptr<Object>(new Object), method2()); //2
上面代码2中由于编译器导致执行顺序问题:
- new Object
- method2()
- new shared_ptr
如果method2出现错误,那么new Object会成为废弃指针。
条款20:尽量用pass-by-reference-to-const替换pass-by-value
- pass-by-reference可以避免切割问题:派生类实参以by-value形式传值给基类形参,此时会调用基类的copy构造函数,所以函数内只有基类,派生类的行为会被阉割掉,如:基类调用virtual成员函数就一定不会调用派生类的对应成员函数。
- “尽量用pass-by-reference-to-const替换pass-by-value”这条规则对于内置类型以及stl的迭代器和函数对象往往并不适用。
条款21:必须返回对象时,别妄想返回其reference
- 绝对不返回pointer或reference指向一个local stack对象;会返回一个reference指向一个heap-allocted对象;或返回pointer或reference指向一个local static对象。
条款27:尽量少做类型转换
- 下述代码将*this(派生类对象)转换为Base,再调用print函数,实际上调用了Base::print函数。而且要注意的是,它调用的并不是当前对象上的函数,而是*this对象的中的Base成分的一个副本。所以此时在Base::print函数对成员的修改并不会影响到*this对象的成员,他只是修改了Base的副本。
class Base {
public:
Base() :b(1) {};
virtual void print() { b = 2; cout << b << endl; }
protected:
int b;
};
class Derived : public Base {
public:
Derived() = default;
void print()
{
static_cast<Base>(*this).print();
cout << b << endl;
}
};
int main()
{
Derived d;
d.print();
return 0;
}
输出:
2
1
从输出可以看出,将*this对象转化为基类对象所做的修改不会对*this有影响。
条款28:避免返回handles(pointer, reference,iterator)
- 类成员函数如果返回一个handles,并且这个handles指向某个private成员,那么就可以通过这个handles修改类中的private成员,非常不安全。如果非要返回这个一个handles,那么就应该返回一个const的handles。
条款31:将文件间的编译依存关系降到最低
- 如果一个文件修改了,那么直接include这个文件或者间接include这个文件的文件都会重新编译。间接include:C include B, B include A, C间接include A。
- 定义某个类的指针或者引用并不需要该类的定义,只需提前声明该类就可以了。
- 当声明一个函数用到另外一个类时,并不需要该类的定义,即使这个函数以by value形式传递