从一道题谈C++中构造函数调用构造函数

来源:互联网 发布:免费种子解析软件 编辑:程序博客网 时间:2024/06/05 16:26
题目如下:问下列代码的打印结果为0吗?

#include <stdlib.h>
#include 
<iostream>
using namespace std;

struct CLS
{
    
int m_i;
    CLS( 
int i ) : m_i(i){}
    CLS()
    {
        CLS(
0);
    }
};
int main()
{
    CLS obj;
    cout 
<< obj.m_i << endl;

    system(
"PAUSE");
    
return 0;
}

打印结果是不定的,不一定为0

代码奇怪的地方在于构造函数中调用了自己的另一个构造函数

我们知道,当定义一个对象时,会按顺序做2件事情:
1)分配好内存(非静态数据成员是未初始化的)
2)调用构造函数(构造函数的本意就是初始化非静态数据成员)

显然上面代码中,CLS obj;这里已经为obj分配了内存,然后调用默认构造函数,但是默认构造函数还未执行完,却调用了另一个构造函数,这样相当于产生了一个匿名的临时CLS对象,它调用CLS(int)构造函数,将这个匿名临时对象自己的数据成员m_i初始化为0;但是obj的数据成员并没有得到初始化。于是obj的m_i是未初始化的,因此其值也是不确定的

从这里,我们归纳如下:
1)在c++里,由于构造函数允许有默认参数,使得这种构造函数调用构造函数来重用代码的需求大为减少
2)如果仅仅为了一个构造函数重用另一个构造函数的代码,那么完全可以把构造函数中的公共部分抽取出来定义一个成员函数(推荐为private),然后在每个需要这个代码的构造函数中调用该函数即可
3)偶尔我们还是希望在类的构造函数里调用另一个构造函数,可以按下面方式做:
在构造函数里调用另一个构造函数的关键是让第二个构造函数在第一次分配好的内存上执行,而不是分配新的内存,这个可以用标准库的placement new做到:

    先看看标准库中placement new的定义

 

inline void *__cdecl operator new(size_t, void *_P)
{
    
return (_P); 

 

可见没有分配新的内存。

正确的方式:

 

struct CLS
{
    
int m_i;
    CLS( 
int i ) : m_i(i){}
    CLS()
    {
        
new (this)CLS(0);
    }
};

 

 


另: 若构造函数调用自身,则会出现无限递归调用,是不允许的

 

 

 

另外******************************************************************************************

把构建责任委托给另一个构造函数,在C++0x可能会有...

下面内容引自
http://zh.wikipedia.org/zh-cn/C%2B%2B0x#.E7.89.A9.E4.BB.B6.E5.BB.BA.E6.A7.8B.E7.9A.84.E6.94.B9.E8.89.AF



对象建构的改良
在标准C++中,建构式不能调用其它的建构式;每个建构式必须自己初始化所有的成员或是调用一个共用的成员函数。基类的建构式不能够直接作为派生类的建构式;就算基类的建构式已经足够,每个衍伸的类仍必须实做自己的建构式。类中non-constant的数据成员不能够在声明的地方被初始化,它们只能在建构式中被初始化。 C++0x将会提供这些问题的解决方案。

C++0x允许建构式调用其他建构式,这种做法称作委托或转接(delegation)。 仅仅只需要加入少量的代码,就能让数个建构式之间达成功能复用(reuse)。 Java以及C#都有提供这种功能。C++0x 语法如下:

C/C++ code
class SomeType { int number; string name; SomeType( int i, string& s ) : number(i), name(s){}public: SomeType( ) : SomeType( 0, "invalid" ){} SomeType( int i ) : SomeType( i, "guest" ){} SomeType( string& s ) : SomeType( 1, s ){ PostInit(); }};


C++03中,建构式运行退出代表对象建构完成; 而允许使用转接建构式的 C++0x 则是以"任何"一个建构式退出代表建构完成。 使用转接的建构式,函数本体中的代码将于被转接的建构式完成后继续运行(如上例的 PostInit())。 若基底类使用了转接建构式,则派生类的建构式会在"所有"基底类的建构式都完成后, 才会开始运行。

C++0x 允许派生类手动继承基底类的建构式, 编译器可以使用基底类的建构式完成派生类的建构。 而将基类的建构式带入派生类的动作, 无法选择性地部分带入, 要不就是继承基类全部的建构式,要不就是一个都不继承(不手动带入)。 此外,若牵涉到多重继承,从多个基底类继承而来的建构式不可以有相同的函数签名(signature)。 而派生类的新加入的建构式也不可以和继承而来的基底建构式有相同的函数签名,因为这相当于重复声明。

语法如下:

C/C++ code
class BaseClass{public: BaseClass(int iValue);}; class DerivedClass : public BaseClass{public: using BaseClass::BaseClass;};

此语法等同于 DerivedClass 声明一个DerivedClass(int) 的建构式。 同时也因为 DerivedClass 有了一个继承而来的建构式,所以不会有默认建构式。

另一方面,C++0x可以使用以下的语法完成成员初始化:

C/C++ code
class SomeClass{public: SomeClass() {} explicit SomeClass(int iNewValue) : iValue(iNewValue) {} private: int iValue = 5;};

若是建构式中没有设置iValue的初始值,则会采用类定义中的成员初始化,令iValue初值为5。在上例中,无参数版本的建构式,iValue便采用默认所定义的值; 而带有一个整数参数的建构式则会以指定的值完成初始化。

成员初始化除了上例中的赋值形式(使用"=")外,也可以采用建构式以及统一形的初始化(uniform initialization,使用"{}")。