如不使用自动生成函数要明确拒绝

对于一个类,如果你没有声明,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函数在构造函数和析构函数中的注意事项的更多相关文章

  1. 【校招面试 之 C/C++】第10题 C++不在构造函数和析构函数中调用虚函数

    1.不要在构造函数中调用虚函数的原因 在概念上,构造函数的工作是为对象进行初始化.在构造函数完成之前,被构造的对象被认为“未完全生成”.当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数, ...

  2. C++进阶--构造函数和析构函数中的虚函数

    //############################################################################ /* 任何时候都不要在构造函数或析构函数中 ...

  3. 在构造函数和析构函数中调用虚函数------新标准c++程序设计

    在构造函数和析构函数中调用虚函数不是多态,因为编译时即可确定调用的是哪个函数.如果本类有该函数,调用的就是本类的函数:如果本类没有,调用的就是直接基类的函数:如果基类没有,调用的就是间接基类的函数,以 ...

  4. c++有关构造函数和析构函数中调用虚函数问题

    今天看了一道迅雷的笔试题目,然后引起一段思考,题目如下: 下列关于虚函数的说法正确的是()A.在构造函数中调用类自己的虚函数,虚函数的动态绑定机制还会生效.B.在析构函数中调用类自己的虚函数,虚函数的 ...

  5. 31.C++-虚函数之构造函数与析构函数分析

    1.构造函数不能为虚函数 当我们将构造函数定义为虚函数时,会直接报错: 首先回忆下以前学的virtual虚函数概念: 如果类定义了虚函数,创建对象时,则会分配内存空间,并且为该父类以及其所有子类的内存 ...

  6. C++类中函数(构造函数、析构函数、拷贝构造函数、赋值构造函数)

    [1]为什么空类可以创建对象呢? 示例代码如下: #include <iostream> using namespace std; class Empty { }; void main() ...

  7. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  8. 09——绝不在构造和析构函数中调用virtual函数

    在base class构造期间,virtual函数不是virtual函数. 构造函数.析构函数中不要调用virtual函数.

  9. Effective C++(9) 构造函数调用virtual函数会发生什么

    问题聚焦: 不要在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果. 让我先来看一下在构造函数里调用一个virtual函数会发生什么结果 Demo class Trans ...

随机推荐

  1. angularjs $watch demo

    <!doctype html> <html lang="en" ng-app> <head> <meta charset="UT ...

  2. Go语言学习笔记一(语法篇)

    国庆节七天假期,这段时间刚好项目那边催的不是很紧,基本上每天都是白天重构一下项目代码,晚上自己学习.(大概是因为容总那边的人都去度假了把项目进度放慢了吧.这两天“彩虹”姐姐也来凑热闹,据说还是直接从澳 ...

  3. C# 字符流打印类

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.W ...

  4. linux中/etc/init.d [转]

    一.关于/etc/init.d 如果你使用过linux系统,那么你一定听说过init.d目录.这个目录到底是干嘛的呢?它归根结底只做了一件事情,但这件事情非同小可,是为整个系统做的,因此它非常重要.i ...

  5. 错误 -force-32bit 与 ANDROID_EMULATOR_FORCE_32BIT=true

    1,配置环境变量, 加上ANDROID_EMULATOR_FORCE_32BIT=true 2,在AS中启动模拟器用下面方法 在你要运行的个工程右击->Run as -> Run conf ...

  6. Python 数据类型转换

    Python提供的基本数据类型主要有:布尔类型.整型.浮点型.字符串.列表.元组.集合.字典.日期等等 函数                      描述     type(x)  x的数据类型   ...

  7. Pascal's Triangle(帕斯卡三角)

    Given numRows, generate the first numRows of Pascal's triangle. For example, given numRows = 5,Retur ...

  8. poj1961Period(next数组)

    http://poj.org/problem?id=1961 对于next数组只能说略懂,其中精髓还是未完全领会 大体是本串相同前缀与后缀的最大长度,读不懂?看串abcdab 这里所说前缀与后缀都为a ...

  9. 彻底搞清javascript中this, constructor, prototype

    说起这三个属性,肯定有一些同学和我一样,初学js时非常困惑,头大,一脸的迷茫.今天就来给大家彻底解决这些担心受怕的问题. 先看this this定义: this就是函数赖以执行的对象. 分析这句话: ...

  10. 【笨嘴拙舌WINDOWS】GDI(1)

    GDI:Graphics Device Interface 图形设备接口. 操作系统从命令行界面到图形界面的过度是施乐公司实验室对计算机普及作出的不可估量的贡献,苹果公司乔布斯与微软公司比尔盖茨对其的 ...