C++开发中级


830 浏览 5 years, 4 months

6.2 继承构造函数(仅限C++11)

版权声明: 转载请注明出处 http://www.codingsoho.com/

继承构造函数(仅限C++11)

在前面的小节中,您己经看到可以在子类中使用using关键字显式地包含超类中定义的方法。在C++11之前,这种方法适用于普通类方法,但不适用于构造函数。C++11解决了这个问题,允许您在子类中继承基类的构造函数。考虑下面的Super以及Sub类定义:

class Super
{
    public:
        Super(const std::string& str);
};

class Sub : Super
{
    public:
        Sub(int i);
};

您只能使用Super提供的构造函数构建Super对象,这个构造函数需要一个字符串参数。另一方面,只能用Sub的构造函数创建Sub对象,这个构造函数需要一个整数作为参数。您无法使用Super类中接收字符串的构造函数创建Sub对象。例如:

    Supper super("Hello"); // OK, calls string based Super ctor
    Sub sub1(1); // OK, calls integer based on Sub ctor
    Sub sub2("Hello"); // KO, Sub don't inherit string super ctor

如果您喜欢使用基于字符串的Super构造函数构建Sub对象,可在Sub类中显式地继承Super构造函数,如下所示:

class Sub : Super
{
    public:
        using Super::Super;
        Sub(int i);
};

现在可以通过下面两种方法构建Sub对象:

    Sub sub1(1); // OK, calls integer based on Sub ctor
    Sub sub2("Hello"); // OK, calls inherited string based on Super ctor

Sub类定义的构造函数可以与从Super类继承的一个构造函数有相同的参数列表。与所有重写一样,此时Sub类的构造函数优先级高于继承的构造函数。在下面的示例中,Sub类使用using关键字继承了Super类的所有构造函数。然而,由于Sub类定义了一个使用浮点数做参数的构造函数,从Super继承的采用浮点数做参数的构造函数被重写。

class Super
{
    public:
        Super(const std::string& str);
        Super(float f);
};

class Sub : Super
{
    public:
        using Super::Super;
        Sub(float f); // Overrides inherited float based Super ctor
};

根据这个定义,可以用下面的代码创建Sub对象:

    Sub sub1("Hello"); // OK, calls inherited string based on Super ctor
    Sub sub2(1.23); // OK, calls float based on Super ctor

使用using语句从基类继承构造函数有一些限制。当从基类继承构造函数时,会继承全部的构造函数,而不可能只继承基类的部分构造函数。第二个限制与多重继承有关。如果一个基类的某个构造函数与另一个基类的构造函数具有相同的参数列表,不可能从基类继承构造函数,因为那样会导致歧义。为了解决这个问题,Sub类必须显式地定义冲突的构造函数。

例如,下面的Sub类试图继承Super1以及Super2类所有的构造函数,这会产生编译器错误,因为用浮点数做参数的构造函数存在歧义。

class Super1
{
    public:
        Super1(float f);
};

class Super2
{
    public:
        Super2(const std::string& str);
        Super2(float f);
};

class Sub : public Super1, public Super2
{
    public:
        using Super1::Super1;
        using Super2::Super2;
        Sub(char c); 
};

Sub中的第一条using句继承了Super 1的构造函数。这意味着Sub将具有如下构造函数:

Sub(float f); // Inherited from Super1

Sub中的第二条using语句试图继承Super2全部的构造函数。然而,这会导致编译器错误,因为这意味着Sub拥有了第二个Sub(float )构造函数。为了解决这个问题,可在Sub类中显式声明冲突的构造函数,如下所示:

class Sub : public Super1, public Super2
{
    public:
        using Super1::Super1;
        using Super2::Super2;
        Sub(char c); 
        Sub(float f); 
};

现在,Sub类显式地声明了一个采用浮点数类型做参数的构造函数,从而解决了歧义问题。如果您愿意,在Sub类中显式声明的用浮点数做参数的构造函数仍然可以在ctor-initializer中调用Super1以及Super2的构造函数,如下所示:

Sub :: Sub(float f) : Super1(f), Super2(f){}

新的编译器中报错

// multiple definition of `Sub::Sub(float)'

当使用继承的构造函数时,要确保所有的成员变量都正确地初始化。例如,考虑下面的Super以及Sub类的新定义。这个示例没有正确地初始化mInt数据成员,在任何情况下这都是一个严重的错误。在示例之后将给出解决方案。

class Super
{
    public:
        Super(const std::string& str) : mStr(str);
    protected:
        std::string mStr;
};

class Sub : public Super
{
    public:
        using Super::Super;
        Sub(int i) : Super(""), mInt(i){}
    protected:
        int mInt;
};

可采用如下方法创建一个Sub对象:

Sub s1(2);

这条语句将调用sub(int i)构造函数,这个构造函数将初始化Sub类的mInt数据成员,并调用Super构造函数用空字符串初始化mStr数据成员。

由于Sub类继承了Super的构造函数,还可以按照下面的方式创建一个Sub对象:

Sub s2("Hello World");

这条语句将调用从Super继承的构造函数。然而,从Super继承的构造函数只初始化了Super类的mStr成员变量,没有初始化Sub类的mint成员变量,mInt处于未初始化状态。通常不建议这么做。

C++11中另一个名为类内成员初始化器的特性可以解决这个问题,在第前面已经讨论过这个特性。下面的代码使用了类内成员初始化将mInt初始化为0。Sub(int i)构造函数仍然可以修改这一初始化,并将mInt初始化为参数i的值。

class Sub : public Super
{
    public:
        using Super::Super;
        Sub(int i) : Super(""), mInt(i){}
    protected:
        int mInt = 0;
};