条款5 了解c++默默编写并调用了哪些函数
(1)
编译器可以暗自为class创建 default构造函数、copy构造函数、copy assignment操作符,以及析构函数。
编译器产出的析构函数是 non-virtual的,除非这个类的基类自身声明有virtual析构函数。
如果作者声明了构造函数,编译器就不会帮忙创建默认构造函数。
class析构函数(无论是编译器生成,或是用户自定义的)会自动调用其non-static成员变量的析构函数。
(2)哪些情况是编译器不会自动生成默认copy赋值操作符
template <typename T>
class NamedObject {
public:
NamedObject(const char* name, const T& value); //我声明了构造函数,编译器就不会自动生成默认的构造函数
NamedObject(const string& name, const T& value);
private:
string namevalue; //如果是string& namevalue; 编译器不会生成默认copy赋值操作符,因为c++不允许让引用改指向不同的对象。
const T objectValue;//如果是const T objectValue; 编译器也不会生成默认copy赋值操作符,因为更改const成员是非法的。
//以上只能自己写copy赋值操作符
};
template <typename T>
NamedObject<T>::NamedObject(const char* name, const T& value):namevalue(name),objectValue(value)
{}
template <typename T>
NamedObject<T>::NamedObject(const string& name, const T& value):namevalue(name),objectValue(value)
{}
int main()
{
NamedObject<int> no1("Smallest Number", 2);
NamedObject<int> no2(no1); //调用了默认copy构造函数与string类的默认copy构造函数
no2 = no1; //调用默认copy赋值操作符
return 0;
}
//还有一种情况,如果将copy赋值操作符声明为private,编译器拒绝为派生类生成copy赋值操作符。
//毕竟编译器所生成的copy赋值操作符想象中可以处理base class成分,但他们无法调用派生类无权调用的成员函数。
条款6 若不想使用编译器自动生成的函数,就应该明确拒绝
下面是怎么让程序不能用copy构造函数和copy赋值操作符。
(1)为了驳回编译器自动(暗自)提供的机能,可将相应的成员函数(copy构造函数、copy assignment操作符)声明为private并且不予实现。
class NamedObject {
public:
NamedObject(const string s);
private:
string namevalue;
NamedObject(const NamedObject&); //我写了copy构造函数并放在private,且可以不实现。 编译器就不会帮我生成默认copy构造函数
NamedObject operator=(const NamedObject&); //同上,只需要写声明,定义就不用写。
};
NamedObject::NamedObject(const string s):namevalue(s){}
int main()
{
NamedObject no1("Smallest Number");
NamedObject no2("Bigest Number");
NamedObject no3(no1); //编译器报错
no2 = no1; //编译器报错
return 0;
}
(2)使用像uncopyable这样的base class也是一种做法。
class Uncopyable{
public: //允许derived对象构造和析构
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&); //阻止copying
Uncopyable operator=(const Uncopyable&);
};
class HomeforSale: private Uncopyable{ //class不再声明copy构造函数或copy赋值操作符
//当尝试拷贝HomeforSale对象时,编译器生成一个copy构造函数,
//这个函数尝试调用基类的对应的构造函数,因为是private,会被编译器拒绝。
};
条款7 为多态基类声明virtual析构函数
(1)
polymorphic(带多态性质)base class 应该声明一个virtual析构函数。
派生类对象经由一个基类指针被销毁,如果析构不是虚函数,derived部分会没被销毁。(面试非常有可能问这个!!!)
如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
(2)
class的设计目的如果不是作为base classes使用,或不是为了具备多态性(polymorphically),就不应该声明virtual析构函数,因为虚表指针也要占内存!
(3)
析构函数调用顺序是先 派生类,后 基类。
(4)
利用纯虚析构函数来生成一个抽象类
class AWOV{ //想拥有一个抽象类,但是没有纯虚函数,就可以把析构函数变为纯虚函数。
public:
virtual ~AWOV() = 0;
};
AWOV::~AWOV(){} //必须为纯虚析构函数提供一份定义,不然编译器报错
class AA: public AWOV
{};
int main()
{
AA* a = new AA();
delete a;
return 0;
}
条款8 别让异常逃离析构函数
(1)
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序。
不然会出现资源泄露。
(2)
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
class DBConnection{ //想拥有一个抽象类,但是没有纯虚函数,就可以把析构函数变为纯虚函数。
public:
static DBConnection create();
void close();
};
class DBconn{
public:
void close() //供客户使用的新函数
{
db.close();
closed = true;
}
~DBconn()
{
if (!closed)
{
try{ //如果客户没有做,我们才帮忙做。
db.close();
}
catch(...){
... //制作运转记录,记下对close的调用失败。结束程序或者吞下异常。
}
}
}
private:
DBConnection db;
bool closed;
};
条款9 绝不在构造和析构过程中调用virtual函数
(1)
在构造和析构期间不要调用virtual函数,因为这类调用不会带来预想的结果。
当派生类构造函数被调用时,首先基类构造函数会被调用,这个时候基类的构造函数里面的虚函数用的是基类的版本而不是派生类的版本。
这是因为在派生类对象在基类构造期间,当前对象的类型是base class,而不是 derived class。相同也适用于析构函数。
所以说需要确定构造和析构函数里面不能有虚函数,不然会发生在基类指针指向派生类对象时的构造函数中,你以为会调用派生类的某个函数,但是却调用了基类的这个函数。
(2)其他解决方案:
无法使用虚函数从基类向下调用,在构造期间,可以用“令派生类将必要的信息向上传递给基类构造函数”替换。
class Transaction{
public:
explicit Transaction(const string& logInfo); //explicit作用是只能显式转换,禁止隐式转换
void logTransaction(const string& logInfo) const; //必须是非虚函数
};
Transaction::Transaction(const string& logInfo)
{
...
logTransaction(logInfo);
}
class BuyTransaction: public Transaction
{
public:
BuyTransaction(param):Transaction( createLogString(param) ) //将log信息传递给基类构造函数
{}
private:
static string createLogString(param); //利用辅助函数创建一个值传给基类构造函数比较方便
}; //函数为static,就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。
//意思是静态成员函数不能调用非静态成员变量。在这个对象的构造函数期间,有些 非静态成员变量 可能还未初始化。
条款10 令operator= 返回一个 reference to *this
(1)
令所有赋值(assignment)操作符返回一个reference to *this,因为有 x=y=z=15这种情况。
class Widget{
public:
Widget(int a):value(a){}
Widget& operator=(const Widget& rhs) //返回一定也是个引用
{
this->value = rhs.value;
return *this;
}
private:
int value;
};
(2)注意:这是一个协议,如果不遵守也可以通过编译。
条款11 在operator= 中处理 “自我救赎”
确保当对象自我赋值时 operator= 有良好行为。其中技术包括
1、比较“来源对象”和“目标对象”的地址,就是看是不是指向同一个地址
2、精心周到的语句顺序,先保存原有的,等新的已经准备好了,再删除原有的
3、copy-and-swap,用一个中间变量 copy and swap
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
class Bitmap{...};
class Widget{
public:
...
private:
Bitmap* bp; //指向一个从heap分配而来的对象
};
//方式一:比较“来源对象”和“目标对象”的地址,就是看是不是指向同一个地址
Widget& Widget::operator=(const Widget &rhs)
{
if (this == &rhs) return *this; //证同测试,如果是自我赋值,就不做任何事
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
//方式二:精心周到的语句顺序,先保存原有的,等新的已经准备好了,再删除原有的
Widget& Widget::operator=(const Widget &rhs)
{
//不需要证同测试,也可以实现自我赋值
Bitmap* pOrig = pb; //先不删,留着新的空间申请成功了再删除。
pb = new Bitmap(*rhs.pb); //指向新的
delete pOrig; //删除原来的
return *this;
}
//方式三:copy-and-swap,用一个中间变量 copy and swap
void swap(Widget& rhs) //交换*this和rhs的数据
{...}
Widget& Widget::operator=(const Widget &rhs)
{
Widget tmp(rhs);
swap(tmp);
return *this;
}
//方式四:copy-and-swap,pass-by-value直接会有一个副本可以用来复制
void swap(Widget& rhs)
{...}
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
条款12 复制对象时勿忘其每一个成分
(1)
copying函数(拷贝构造和operator=函数)应该确保复制 对象内的所有成员变量 及 调用所有基类内的适当的copying函数。
class Customer{
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
...
private:
string name;
};
Customer::Customer(const Customer& rhs):name(rhs.name){}
Customer& Customer::operator=(const Customer& rhs)
{
this->name = rhs.name;
return *this;
}
class PriorityCustomer: public Customer{
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
...
private:
int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs), //派生类构造时一定不要忘了调用基类的copy构造函数
priority(rhs.priority){}
PriorityCustomer::PriorityCustomer& operator=(const PriorityCustomer& rhs)
{
Customer::operator=(rhs); //对基类成分进行赋值操作
this->priority = rhs.priority;
return *this;
}
(2)
不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数(一般是private)中,并由两个coping函数共同调用。