Tips05 - 了解C++默写并调用了哪些函数
当我们申明一个空类时,C++编译器会自动为我们什么一个copy构造函数、一个copy assignment操作符、一个析构函数以及一个默认构造函数。且这些函数都是public和inline的。
我们写的:
class Empty{};
编译器生成的:
class Empty{
public:
Empty(){...} //默认构造函数
Empty(const Empty& rhs){...} //copy构造函数
~Empty(){} //析构函数
Empty& operator=(const Empty& rhs){...} //copy assignment操作符
};
当我们进行如下操作时,它们才会被编译器创建。
Empty e1;
Empty e2(e1);
e2 = e1;
ps:编译器产生的析构函数时非虚函数,除非该类的基类自身声明有虚析构函数。
对于copy构造函数和copy assignment,在某些情况下,编译器会拒绝执行为它们创建的这些函数(如果它们被调用的话)。
比如:
template<class T>
class NamedObject{
public:
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue; //引用
const T objectValue; //常量类型
}
std::string newDog("persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 35)
p = s;
;
由于缺少copy构造函数,系统会自动为它们创建一个默认的。
在p = s 之前,p.nameValue 和 s.nameValue 分别指向newDog和 oldDog。但当p = s时,如果这条操作执行,那么p.nameValue就会指向s.nameValue所指向的那个值,这样就改变了引用自身,这种操作在C++中是不能执行的。所以在这种情况下,C++的响应是拒绝编译赋值那一行。
const成员同理,在C++中,const的成员是无法改变的,所以C++也是无法执行的,所以只能自己定义copy assignment操作符。
最后就是如果某个base class将copy assognment 声明为private 编译器将拒绝为其derived class 生成一个copy assignment操作符。
**记住:**编译器可以暗自为class创建默认构造函数、copy构造函数、copy assignment操作符以及析构函数。
Tips06 - 如果不想使用编译器自动生成的函数,就应该明确拒绝
由tips05可以知道,如果在声明类的时候没有提供上述几个函数,那么系统会自动为它们创建,所以我们需要自己创建这些函数。如果你想阻止这些操作的发生,可以将它们声明为private,这样既能防止编译器自动生成,又能阻止人们调用它们。
但这样做的问题在于,成员函数 和 友元函数是可以调用它们。所以更好的办法就是:将这些成员函数声明为private而且不去实现它们。
比如:
class HomeForSale{
public:
...
private:
...
HomeForSale(const HomeForSales&);
HomeForSale& operator=(const HomeForSale&);
}
有了上述class定义,当客户企图拷贝HomeForSale对象,编译器会阻止他,但如果在member函数或friend函数之内这么做,那么连接器就会报错。
只需要将copy构造函数和copy assignment操作符声明为private就可以办到,但不是在HomeForSale自身,而在一个专门为了阻止copy动作而设计的base class内。
class Uncopyable{
public:
...
Uncopyable(){}
~Uncopyable(){}
private:
...
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale: private Uncopyable{...};
当友元或者member函数尝试拷贝HomeForSale对象时,编译器会尝试生成一个copy构造函数和一个copy assignment操作符,但这种调用会被拒绝,因为base class 的拷贝函数时private的。
**记住:**为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用Uncopyalbe这样的base class也是一种做法。
Tips07 - 为多态基类声明virtual析构函数
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper{...};
class WaterClock: public TimeKeeper{...};
class WristWatch: public TimeKeeper{...};
TimeKeeper* getTimeKeeper(); //返回一个指针,指向一个TimeKeeper派生类的动态分配对象
TimeKeeper* ptk = getTimeKeeper();
...
delete ptk;
该段代码的问题在于getTimeKeeper返回的指针指向一个derived class对象,而那个对象却经过一个base class指针被删除,而目前这个base class 的析构函数是个non-virtual。这会导致在实际执行时该对象的derived的成分没有被销毁。这是一个局部销毁对象。很可能导致资源泄漏、败坏等问题。
解决这个问题最简单的做法,就是把base class定义为virtual析构函数。
class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
需要注意的的:如果一个class不被企图当作base class时,让其析构函数成为virtual是一个馊主意,因为这会导致增加额外的开销。
因此,无端地将所有class地析构函数声明为virtual,就像从未声明它们virtual一样,都是错误的。许多人的心得是:只有当class内至少含有一个virtual函数,才为它申明virtual析构函数。
**注意:**polymorphic(带有多态性质的)base class应该声明一个virtual析构函数,如果class有用任何virtual函数,它就应该拥有一个virtual析构函数。
Class的设计目的如果不是作为base class使用,或不是为了具备多态性,就不应该声明virtual析构函数。
tips08 别让异常逃离析构函数
C++并不进制析构函数吐出异常,但它不鼓励这么做。理由如下:
class Widget{
public:
...
~Widget(){...}//假设可能会吐出异常
};
void doSomething()
{
std::vector<Widget>v;
...//v被自动销毁
}
当v被销毁,会自动销毁所有的Widgets,假设有10个Widgets,析构第一个时抛入异常,其他九个还是应该销毁,但如果在销毁后面9个时继续抛出异常。现在有两个同时抛出异常,这对于C++来说太多了。在两个异常同时存在时,程序弱不是执行结束就是导致不明确行为。
**记住:**析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能会抛出异常,析构函数应该捕获任何异常,然后吞下它们或者结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。