C++构造函数语义——默认构造函数

原创 2012年03月26日 15:19:20

0、前言


  《The C++ ARM》告诉我们:“默认构造函数会在需要的时候自动生成(被编译器)。”然后“在需要的时候”是一个很抽象的概念,本文主要描述的问题也正是这些需要的情况。

  我们看看下面的代码片段:

class Foo
{  
public:
    int val;
    Foo *pnext;
};
void foo_bar()
{  
    Foo bar;
    if (bar.val || bar.pnext)
    {
        cout << bar.val << endl;
        cout << bar.pnext << endl;
    }
}
用户并没有显示地定义默认构造函数,编译器会为它自动生成一个无关紧要(trivial)的默认构造函数,生成的默认构造函数什么也不错,既不会讲其成员变量置零,也不会做其他的任何事情,只是为了保证程序能够正确运行而已,这就是所谓的“需要”,如果还需要给初始化成员变量,这件事情还是交给程序员做吧!


一、非平凡(non-trivival)默认构造函数


C++标准描述了哪些情况,这样的隐式默认构造函数是无关紧要的。一个非平凡(non-trivival)的默认构造函数是ARM中所说的被实现所“需要”,并在必要的时候被编译器自动生成。下面来看看默认构造函数是非平凡的四种情况:


1.1 含有包含默认构造函数的成员类对象


如果该类包含一个成员类对象,它有默认的构造函数,那么这个类的隐式构造函数是非平凡的,并且编译器需要为包含的这个成员类对象生成一个默认构造函数。然后,这个编译器生成的默认构造函数只有在实际上被调用时才会被真正的生成。

class Foo
{
public:

    Foo(){ _i = 1; }
    int _i;
};
class Bar
{
public:
    Foo foo;
    char *str;
};
void foo_bar()
{
    Bar bar;
}

  在这个程序片段中Bar的成员foo含有默认构造函数,它初始化自己的类成员_i为1而Bar本身并没有定义默认的构造函数,这个构造函数的目的是为了初始化它的成员变量foo,实际上就是调用Bar::foo的默认构造函数,但它并不会做一丁点关于另外一个变量str的初始化和赋值工作,初始化Bar::foo是编译器的责任,二初始化str是程序员的。我们可以用以下代码来大致描述一下编译器的工作:

inline 
Bar::Bar() 
{ 
   // Pseudo C++ Code 
   foo.Foo::Foo(); 
}

  如果这里的Bar含有默认构造函数呢?我们可以从编译器和程序员的责任划分来考虑这个问题:

  1. Bar的默认构造函数调用foo的默认构造函数,那么编译器啥也不用做了。

  2. Bar的默认构造函数中没有去foo这个成员变量,那么编译器需要去帮助程序员把这件事情做完,插入一条类似“foo.Foo::Foo();”的代码。

注:如果Bar含有多个成员类变量,则编译器会按照这些变量的声明顺序去做以上处理。


1.2 含有包含默认构造函数的基类


  类似的,要是一个继承的基类包含默认构造函数而该类本身没有任务的构造函数,那么编译器会生成一个默认构造函数,目的是初始化它的基类。

  当程序员为该类定义了多个构造函数,就是没定义默认构造函数呢?

  在这种情况下,编译器会在每一个构造函数中增加(augment)有关调用基类的默认构造函数部分代码。


1.3 含有虚函数


  生成的默认构造函数是必须的当另外两个额外条件(满足其一):

  1.该类定义了(或继承了)虚函数。

  2.在该类的继承关系中,有一个或更多的虚基类。

  可以参考一下的类继承关系:

class Widget
{
public:
    virtual void flip() = 0;
};
class Bell: public Widget
{
public:
    void flip(){ cout <<"Bell." << endl; }
};
class Whistle: public Widget
{
public:
    void flip(){ cout <<"Whistle." << endl; }
};

void flip(Widget &widget)
{
    widget.flip();
}

void foo()
{
    Bell b;
    Whistle w;
    flip(b);
    flip(w);
}

  在编译时,在默认构造函数中会发生下面的两个类扩充(augmentation):

  1.虚表会被产生,其内容被这个类的活动(active)虚函数填充。

  2.编译器为每个类对象生成一个虚指针(vtbl)。


1.4 含有一个虚基类


  关于虚基类的内容,本文暂不详述,需要记住的是:编译器会为该类添加一个类似虚指针的东西——“_vbcX”。


二、参考文献


  《Inside The C++ Object Model》


C++11新特性之 Move semantics(移动语义)

按值传递的意义是什么? 当一个函数的参数按值传递时,这就会进行拷贝。当然,编译器懂得如何去拷贝。 而对于我们自定义的类型,我们也许需要提供拷贝构造函数。但是不得不说,拷贝的代价是昂贵的。所以我们需...
  • wangshubo1989
  • wangshubo1989
  • 2015年11月09日 23:57
  • 3650

语义分析的一些方法(一)

语义分析,本文指运用各种机器学习方法,挖掘与学习文本、图片等的深层次概念。wikipedia上的解释:In machine learning, semantic analysis of a corpu...
  • haidao2009
  • haidao2009
  • 2015年06月02日 10:28
  • 5472

C++构造函数语义——默认构造函数

0、前言   《The C++ ARM》告诉我们:“默认构造函数会在需要的时候自动生成(被编译器)。”然后“在需要的时候”是一个很抽象的概念,本文主要描述的问题也正是这些需要的情...
  • twlkyao
  • twlkyao
  • 2013年05月28日 11:00
  • 718

[C++]右值引用和转移语义

右值引用和转移语义 本文尝试着解释何为右值引用和转移语义以及使用它们具有优势,并提供相关案例分析。 定义左值和右值首先我们先来理解一下什么是左值和右值。 C/C++语言中可以放在赋值符号左边的...
  • stary_yan
  • stary_yan
  • 2016年04月30日 00:06
  • 4498

C++10中的移动语义

首先,我们来看这样一个函数: (T为一个对象类型)T clone(const T& rhs) { T other = rhs; return other; }这样的函数中,其实会调用...
  • T_27080901
  • T_27080901
  • 2016年01月13日 18:20
  • 621

深度RNN解决语义搜索难题

深度RNN解决语义搜索难题 2016-08-15 22:34 转载 CSDN 0条评论 雷锋网(搜索“雷锋网”公众号关注)按:本文作者张俊林,主要介绍了3种基于深度R...
  • Real_Myth
  • Real_Myth
  • 2016年08月22日 09:25
  • 1452

[读书笔记] 深入探索C++对象模型-第五章-构造、析构、拷贝语义学(中)

继续整理第五章的内容,关于对象复制的。  对于默认的拷贝赋值操作符,在如下情况下不会表现出按位拷贝(bitwise copy:关于按位拷贝,实际就是不使用拷贝构造函数或者拷贝赋值操作符,这里的不使用是...
  • beyongwang
  • beyongwang
  • 2016年09月22日 23:43
  • 334

移动语义(move semantic)和完美转发(perfect forward)

https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/   新标准重新定义了lvalue和rvalue,并...
  • suchto
  • suchto
  • 2017年02月09日 15:42
  • 498

c++move语义与右值引用

// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is gover...
  • xdgs_2005
  • xdgs_2005
  • 2015年03月05日 12:20
  • 2171

C++对象语义与值语义

C++对象语义与值语义 一、值语义 所谓值语义是一个对象被系统标准的复制方式复制后,与被复制的对象之间毫无关系,可以彼此独立改变互不影响 实现   ...
  • chenglinhust
  • chenglinhust
  • 2013年08月12日 15:32
  • 1114
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++构造函数语义——默认构造函数
举报原因:
原因补充:

(最多只允许输入30个字)