5.了解C++默默编写并调用哪些函数。
当你定义一个空类,c++默认会给你加上一些函数,但是惟有当这些函数被需要(被调用),它们才会被编译器创建出来。如下:
class CEmpty{};
相当于:
class CEmpty
{
public:
CEmpty(){..}
CEmpty(const CEmpty& rhs){...}
~CEmpty(){...}//编译器产出的析构函数没有virtual,除非基类析构函数带有virtual
CEmpty& operator=(const CEmpty& rhs){...}
};
至于copy构造函数和copy赋值操作符,编译器创建的版本只是单纯地将来源对象的每一个non-static成员变量拷贝到目标对象。
如果你打算在一个“内含引用成员”或者“内含const成员”的类内支持赋值操作,你必须自己定义 赋值操作符函数,更改引用和const
变量是不合法的。
如果基类将赋值操作符函数或者复制构造函数声明为private,编译器不会为派生类声明赋值操作符函数和复制构造函数。
6.若不想使用编译器自动生成的函数,就该明确拒绝。
如果要想让类禁用 复制构造 和 赋值操作,则必须将 “复制构造函数”和“赋值操作函数”声明为private,并且不能实现它。
例:
class HomeForSale
{
public:
...
private:
...
HomeForSale(const HomeForSale&);//只声明
HomeForSale& operator=(const HomeForSale&);//只声明
};
以上可能会报链接错误。
下面的方式不会报链接错误。
class Uncopyable
{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable
{...};
总结:为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现,使用像Uncopyable这样的base class也是一种做法。
7.为多态基类声明virtual析构函数
c++明确指出,当derived class 对象经由一个base class 指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义--实际
执行时通常发生的是对象的derived成分没有被销毁。于是造成一个诡异的“局部销毁”对象,形成资源泄漏,数据破坏。
任何 class 只要带有virtual函数都几乎确定应该也有一个virtual析构函数。
如果class 不含virtual函数,通常表示它并不意图被用做一个base class.
如果一个类没有虚函数,你可以把这个类当成结构体使用,但是如果有虚函数,就会有一个虚函数表指针,这样就不能单纯的当一个结构体使用了。
如果想声明一个
总结:
a.带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,就应该拥有一个virtual析构函数。
b.classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
8.别让异常逃离析构函数。
析构函数如何处理异常的例子:
class DBConnection
{
public:
...
static DBConnection create();//这个函数返回DBConnection对象
void close();
};
class DBConn //这个class用来管理DBConnection对象
{
public:
...
~DBConn() //确保数据库连接总是会被管理
{
db.close();
}
private:
DBConnection db;
};
DBConn dbc(DBConnection::create());//dbc被析构会关闭数据库连接
异常处理的几种不同方式:
DBConn::~DBConn()
{
try{db.close}
catch(...)
{
...
std::abort();
}
}
DBConn::~DBConn()
{
try{db.close();}
{
...
}
}
class DBConn
{
public:
...
void close()
{
db.close();
closed=true;
}
~DBConn()
{
if(!closed)
{
try
{
db.close();
}
catch(...)
{...}
}
}
};
总结:
a.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们或结束程序。
b.如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该函数。
9.绝不在构造和析构过程中调用virtual函数。
总结:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
10.令operator=返回一个reference to *this。
11.在operator=中处理“自我赋值”。
Widget& Widget::operator=(const Widget& rhs)
{
if(this!=&rhs)
{
delete pb;
pb=new Bitmap(*rhs.pb)
}
return *this;
}
12.复制对象时勿忘记其每一个成分。
任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。
Customer为基类 PriorityCustomer为派生类。
PriorityCustomer::PriorityCustomer(const PriorityCustomer &rhs)
:Customer(rhs),
priority(rhs.priority)
{
...
}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
Customer::operator=(rhs);
priority=rhs.priority;
return *this;
}
总结:
a.copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。
b.不要尝试以某个copying函数实现另外一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。
当你定义一个空类,c++默认会给你加上一些函数,但是惟有当这些函数被需要(被调用),它们才会被编译器创建出来。如下:
class CEmpty{};
相当于:
class CEmpty
{
public:
CEmpty(){..}
CEmpty(const CEmpty& rhs){...}
~CEmpty(){...}//编译器产出的析构函数没有virtual,除非基类析构函数带有virtual
CEmpty& operator=(const CEmpty& rhs){...}
};
至于copy构造函数和copy赋值操作符,编译器创建的版本只是单纯地将来源对象的每一个non-static成员变量拷贝到目标对象。
如果你打算在一个“内含引用成员”或者“内含const成员”的类内支持赋值操作,你必须自己定义 赋值操作符函数,更改引用和const
变量是不合法的。
如果基类将赋值操作符函数或者复制构造函数声明为private,编译器不会为派生类声明赋值操作符函数和复制构造函数。
6.若不想使用编译器自动生成的函数,就该明确拒绝。
如果要想让类禁用 复制构造 和 赋值操作,则必须将 “复制构造函数”和“赋值操作函数”声明为private,并且不能实现它。
例:
class HomeForSale
{
public:
...
private:
...
HomeForSale(const HomeForSale&);//只声明
HomeForSale& operator=(const HomeForSale&);//只声明
};
以上可能会报链接错误。
下面的方式不会报链接错误。
class Uncopyable
{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable
{...};
总结:为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现,使用像Uncopyable这样的base class也是一种做法。
7.为多态基类声明virtual析构函数
c++明确指出,当derived class 对象经由一个base class 指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义--实际
执行时通常发生的是对象的derived成分没有被销毁。于是造成一个诡异的“局部销毁”对象,形成资源泄漏,数据破坏。
任何 class 只要带有virtual函数都几乎确定应该也有一个virtual析构函数。
如果class 不含virtual函数,通常表示它并不意图被用做一个base class.
如果一个类没有虚函数,你可以把这个类当成结构体使用,但是如果有虚函数,就会有一个虚函数表指针,这样就不能单纯的当一个结构体使用了。
如果想声明一个
总结:
a.带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,就应该拥有一个virtual析构函数。
b.classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
8.别让异常逃离析构函数。
析构函数如何处理异常的例子:
class DBConnection
{
public:
...
static DBConnection create();//这个函数返回DBConnection对象
void close();
};
class DBConn //这个class用来管理DBConnection对象
{
public:
...
~DBConn() //确保数据库连接总是会被管理
{
db.close();
}
private:
DBConnection db;
};
DBConn dbc(DBConnection::create());//dbc被析构会关闭数据库连接
异常处理的几种不同方式:
DBConn::~DBConn()
{
try{db.close}
catch(...)
{
...
std::abort();
}
}
DBConn::~DBConn()
{
try{db.close();}
{
...
}
}
class DBConn
{
public:
...
void close()
{
db.close();
closed=true;
}
~DBConn()
{
if(!closed)
{
try
{
db.close();
}
catch(...)
{...}
}
}
};
总结:
a.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们或结束程序。
b.如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该函数。
9.绝不在构造和析构过程中调用virtual函数。
总结:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
10.令operator=返回一个reference to *this。
11.在operator=中处理“自我赋值”。
Widget& Widget::operator=(const Widget& rhs)
{
if(this!=&rhs)
{
delete pb;
pb=new Bitmap(*rhs.pb)
}
return *this;
}
12.复制对象时勿忘记其每一个成分。
任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心地也复制其base class成分。
Customer为基类 PriorityCustomer为派生类。
PriorityCustomer::PriorityCustomer(const PriorityCustomer &rhs)
:Customer(rhs),
priority(rhs.priority)
{
...
}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
Customer::operator=(rhs);
priority=rhs.priority;
return *this;
}
总结:
a.copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。
b.不要尝试以某个copying函数实现另外一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。