effective c++:virtual函数在构造函数和析构函数中的注意事项
如不使用自动生成函数要明确拒绝
对于一个类,如果你没有声明,c++会自动生成一个构造函数,一个析构函数,一个copy构造函数和一个copy assignment操作符。
class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... } // destructor — see below
// for whether it’s virtual
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
一般来讲,如果你不想有某个功能,不定义对应函数即可,但对与自动生成的函数,当某些人试图使用它,编译器会自动的生成。
遇到这种情况我们就需要private帮忙,因为编译器产生的都是public函数,如果我们在private中声明,编译器就不会再次创建相同的函数,同时也阻止了别人的调用。但是当成员函数和friend函数还是可以调用private函数。所以如果要做彻底一点,我们可以不去定义,只是声明,如果不管是谁使用这些函数,都会得到一个链接错误。
class HomeForSale {
public:
...
private:
...
HomeForSale(const HomeForSale&); // declarations only
HomeForSale& operator=(const HomeForSale&);
};
为多态声明virtual析构函数
首先设计一个TimeKeeper base class 和derived class作为不同的计时方法:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristWatch: public TimeKeeper { ... };
TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamic-
// ally allocated object of a class
// derived from TimeKeeper
返回一个指针指向一个TimeKeeper派生类动态分配对象,因此为了避免内存泄露,应该把创建的对象delete掉:
TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object
// from TimeKeeper hierarchy
... // use it
delete ptk; // release it to avoid resource leak
但是这样还是出现了内存泄露,问题出在基类的non-virtual析构函数,对象经过基类指针被删除,而实际上派生类的成分还没被销毁,派生类的析构函数没有能够执行起来。
我们可以在基类析构函数前加一个virtual 就会完整的销毁整个对象。
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk; // now behaves correctly
虚函数的目的是重新实现基类的功能,任何class带有虚函数都需要一个虚析构函数。
当class中不存在虚函数,使其析构函数为virtual是错误的。一个虚函数的实现往往需要附带一些额外的信息来确定编译器在调用虚函数时找到正确的函数指针,这就使得这个由c++写成的类也不再和其他语言相同的声明有着同样的结构,因此也不再具备可移植性。
所以只有当class内含有至少一个虚函数时,我们才声明虚析构函数,而且当class设计不是为了当基类时(没有虚函数),或不是为了具备多态性,就不该声明虚析构函数。
别让异常逃离析构函数
试想,如果对象出了异常,现在异常处理模块为了维护系统对象数据的一致性,避免资源泄漏,有责任释放这个对象的资源,调用对象的析构函数,可现在假如析构过程又再出现异常,那么请问由谁来保证这个对象的资源释放呢?而且这新出现的异常又由谁来处理呢?不要忘记前面的一个异常目前都还没有处理结束,因此这就陷入了一个矛盾之中,或者说无限的递归嵌套之中。
所以需要在析构函数中做一些额外的工作来避免这一问题,
方法之一在析构函数中抛出异常就abort,例如下面类DBConn调用的析构函数
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
std::abort();
}
}
或者吞下调用close而发生的异常。
如果客户需要对某个操作函数运行期间做出反应,那么class应该提供一个普通函数来执行该操作。
class DBConn {
public:
...
void close() // new function for
{ // client use
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try { // close the connection
db.close(); // if the client didn’t
}
catch (...) { // if closing fails,
make log entry that call to close failed; // note that and
... // terminate or swallow
}
}
}
private:
DBConnection db;
bool closed;
};
不要在构造和析构过程中调用virtual函数
先来看这样一段代码:
class Transaction { // base class for all
public: // transactions
Transaction();
virtual void logTransaction() const = ; // make type-dependent
// log entry
...
};
Transaction::Transaction() // implementation of
{ // base class ctor
...
logTransaction(); // as final action, log this
} // transaction
class BuyTransaction: public Transaction { // derived class
public:
virtual void logTransaction() const; // how to log trans-
// actions of this type
...
};
class SellTransaction: public Transaction { // derived class
public:
virtual void logTransaction() const; // how to log trans-
// actions of this type
...
};
当我们执行BuyTransaction b;这条语句时会调用BuyTransaction 构造函数,但在这之前会首先调用Transaction构造函数,而首先调用的logTransaction也是Transaction版本,这个可以直观的理解为,在Transaction构造时BuyTransaction 尚未开始构造,从而BuyTransaction 中的成员变量也还没有初始化,而虚函数logTransaction几乎一定会调用BuyTransaction 中的成员变量,显而易见调用未经初始化的变量是不允许的,所以基类构造函数中的虚函数形同虚设,析构函数同理。而且如果基类虚函数有实体的话,程序运行期间很难发现原本要调用派生类版本的函数变成了基类版本函数。
唯一能解决 这一问题的方法是在构造和析构函数中不要使用虚函数。例如上例,logTransaction改为non-virtual,在BuyTransaction 构造时传递参数给Transaction
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const; // now a non-
// virtual func
...
};
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo); // now a non-
} // virtual call
class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
: Transaction(createLogString( parameters )) // pass log info
{ ... } // to base class
... // constructor
private:
static std::string createLogString( parameters );
};
effective c++:virtual函数在构造函数和析构函数中的注意事项的更多相关文章
- 【校招面试 之 C/C++】第10题 C++不在构造函数和析构函数中调用虚函数
1.不要在构造函数中调用虚函数的原因 在概念上,构造函数的工作是为对象进行初始化.在构造函数完成之前,被构造的对象被认为“未完全生成”.当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数, ...
- C++进阶--构造函数和析构函数中的虚函数
//############################################################################ /* 任何时候都不要在构造函数或析构函数中 ...
- 在构造函数和析构函数中调用虚函数------新标准c++程序设计
在构造函数和析构函数中调用虚函数不是多态,因为编译时即可确定调用的是哪个函数.如果本类有该函数,调用的就是本类的函数:如果本类没有,调用的就是直接基类的函数:如果基类没有,调用的就是间接基类的函数,以 ...
- c++有关构造函数和析构函数中调用虚函数问题
今天看了一道迅雷的笔试题目,然后引起一段思考,题目如下: 下列关于虚函数的说法正确的是()A.在构造函数中调用类自己的虚函数,虚函数的动态绑定机制还会生效.B.在析构函数中调用类自己的虚函数,虚函数的 ...
- 31.C++-虚函数之构造函数与析构函数分析
1.构造函数不能为虚函数 当我们将构造函数定义为虚函数时,会直接报错: 首先回忆下以前学的virtual虚函数概念: 如果类定义了虚函数,创建对象时,则会分配内存空间,并且为该父类以及其所有子类的内存 ...
- C++类中函数(构造函数、析构函数、拷贝构造函数、赋值构造函数)
[1]为什么空类可以创建对象呢? 示例代码如下: #include <iostream> using namespace std; class Empty { }; void main() ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- 09——绝不在构造和析构函数中调用virtual函数
在base class构造期间,virtual函数不是virtual函数. 构造函数.析构函数中不要调用virtual函数.
- Effective C++(9) 构造函数调用virtual函数会发生什么
问题聚焦: 不要在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果. 让我先来看一下在构造函数里调用一个virtual函数会发生什么结果 Demo class Trans ...
随机推荐
- 局域网聊天软件(winsocket)
LANChat工作整理 2013/8/22 程序实现功能: 局域网聊天软件,启动即可找到在线设备,并能够进行简单的文字聊天. 其实下面这个框图已经说明了程序的绝大部分功能原理. 核心类的程序框图 我觉 ...
- Volley HTTP库系列教程(3)自定义RequestQueue和编写单例RequestQueue示例
Setting Up a RequestQueue Previous Next This lesson teaches you to Set Up a Network and Cache Use a ...
- 第三方登录(1)OAuth(开放授权)简介及授权过程
3个角色:服务方,开发者,用户 a.用户在第在服务注册填写个人信息, b.服务方开放OAuth, c.开发者在服务方申请第3方登录,在程序中得到令牌后,经用户同意,可得到用户的个人信息. OAuth ...
- Python Mongo操作
# -*- coding: utf-8 -*- ''' Python Mongo操作Demo Done: ''' from pymongo import MongoClient conn = None ...
- ubuntu 安装 rabbitmq-server
Rabbitmq 是用 erlang 语言写的,所以我们需要安装 Erlang,安装 erlang 又需要安装 python 与 simplejson,所以我们从python开始: 1.安装 pyth ...
- jsonp获取服务器数据的方式
jsonp获取服务器的数据,有两种 一,跨域 二,不跨域 如果跨域 js的写法有两种 1, <script type="text/javascript"> $(func ...
- URAL1057. Amount of Degrees(DP)
1057 简单的数位DP 刚开始全以2进制来算的 后来发现要找最接近x,y值的那个基于b进制的0,1组合 #include <iostream> #include<cstdio&g ...
- 转载 近期微博吐槽言论存档,涉及“性能优化”、C++陋习等
http://blog.csdn.net/solstice/article/details/9923615 近期微吐槽博言论存档,涉及“性能优化”.C++陋习等 写C++程序的几个陋习:class 名 ...
- 如何在Android studio中同时打开多个工程? (转载)
最近学习Android Studio,想同时打开两个Project.但是点击File->Open之后,原有的Project被关闭掉了.怎么在新的窗口中打开Project呢? 解决: 点击Help ...
- UVa 575 Skew Binary 歪斜二进制
呵呵,这个翻译还是很直白的嘛,大家意会就好. 第一次看到这个高大上题目还是有点小害怕的,还好题没有做过深的文章. 只要按照规则转化成十进制就好了,而且题目本身也说了最大不超过一个int的范围(2^31 ...