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

对于一个类,如果你没有声明,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. Data Base MySQL的常用命令

       MySQL的常用命令 一.下载地址: http://www.mysql.com 二.安装注意: root默认密码:123456 三.常用命令: 1.创建用户并授权: 创建用户,只能本地访问:cr ...

  2. 《Linux/Unix系统编程手册》读书笔记 目录

    <Linux/Unix系统编程手册>读书笔记1  (创建于4月3日,最后更新4月7日) <Linux/Unix系统编程手册>读书笔记2  (创建于4月9日,最后更新4月10日) ...

  3. 在linux系统下检查postgresql数据库安装,登录数据库及简单的查看数据库

    1.    检查Linux系统是否安装数据库 首先查看自己的系统是否安装了postgresql数据库命令如下: rpm -qa | grep postgresql 如果没有显示查询结果(如下图所示)说 ...

  4. R语言记录程序运行的时间

    f <- function(start_time) { start_time <- as.POSIXct(start_time) dt <- difftime(Sys.time(), ...

  5. 一、Oracle分析函数入门

    分析函数是什么?分析函数是Oracle专门用于解决复杂报表统计需求的功能强大的函数,它可以在数据中进行分组然后计算基于组的某种统计值,并且每一组的每一行都可以返回一个统计值. 分析函数和聚合函数的不同 ...

  6. You can't specify target table 'charge' for update in FROM clause

    mysql中不能这么用. (等待mysql升级吧)错误提示就是说,不能先select出同一表中的某些值,再update这个表(在同一语句中) 替 换方 案: create table tmp as s ...

  7. poj 2773(容斥原理)

    容斥原理入门题吧. Happy 2006 Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 9798   Accepted: 3 ...

  8. 宏ut_mem_block_t

    /** This struct is placed first in every allocated memory block */ typedef struct ut_mem_block_struc ...

  9. open_binary_frm

    参数uchar* head 是已经分配好内存的64个字节的地址 http://mysql.taobao.org/monthly/2015/08/07/ /** *先从.frm文件读取64字节 *第28 ...

  10. MVC+Ef项目(1) 项目的框架搭建

    一:首先我们来搭建最基本的项目框架,这里使用MVC3作为web项目,然后我们添加几个类库项目 最后的项目如下, 其中有一个 YouJiao.MvcWeb.Repository 实际就当做是 DAL层即 ...