C++11:继承构造函数和委派构造函数

继承构造函数

在 C++ 中,由于派生类会隐藏掉基类的构造函数,如果想让派生类的构造方式和基类相同,就必须在派生类中重新定义一套和基类一摸一样的构造函数。例如:

struct A
{
    A() { }
    A(int i) { }
    // ...
};

struct B : public A
{
    // ...
};

int main()
{
    B b(1); // Error,没有匹配的构造函数
}

这里想通过一个 int 值构造 B 的方式就行不通,因为派生类 B 把基类 A 的构造函数隐藏了,所以 B 必须这样定义才可以:

struct B : public A
{
    B() : A() { }
    B(int i) : A(i) { }
};

但如果基类有多个不同的构造函数,这种限制就会让代码写起来十分麻烦。例如:

struct A
{
    A() { }
    A(int i) { }
    A(double d) { }
    A(const char* s) { }
    // ...
};

struct B : public A
{
    B() : A() { }
    B(int i) : A(i) { }
    B(double d) : A(d) { }
    B(const char* s) : A(s) { }

    void foo() { }
};

派生类 B 只是新增了一个功能函数接口 foo,并没有扩展任何的数据成员,但是仍然需要重新写一遍与基类 A 完全相同形式的构造函数,才能继承和使用 A 的构造方式,这显然十分不合理。

为了解决这种问题,C++11 允许使用 using 语句,使派生类直接继承基类的构造函数,下面例子是使用 C++11 新标准的实现:

struct A
{
    A() { }
    A(int i) { }
    A(double d) { }
    A(const char* s) { }
    // ...
};

struct B : public A
{
    using A::A; // 可以直接使用A的构造函数
    void foo() { }
};

int main()
{
    B b1(1);
    B b2(2.1);
    B b3("abc");
    return 0;
}

在派生类 B 的定义中,using A::A 使得它可以直接使用基类 A 的构造函数,这样一来就大大简化了我们的代码编写。

实际上这个 using 的语法在 C++11 之前就存在,用于让派生类能够使用被隐藏掉的基类的重名函数,例如:

struct A
{
    void foo();
};

struct B : public A
{
    void foo(int i);
    using A::foo; // 使派生类能够使用A中无参数版本的foo函数
};

但这种机制之前不能用于构造函数,C++11 使扩展了这种 using 的使用范围,允许用于构造函数。

委派构造函数

当成员变量比较多,或者初始化逻辑比较复杂时,如果存在多个构造函数,在每个构造函数中都执行一遍初始化逻辑就会比较麻烦,也显得重复冗余。C++11 允许在一个构造函数中调用另一个构造函数,从而可以在一些情况下简化一些初始化的代码,例如:

struct Foo
{
    Foo(int i, char c, double d) { /*其它一些初始化的逻辑*/ } // 1
    Foo(int i) : Foo(i, 'a', 3.0)  { } // 2
    Foo(char c) : Foo(0, c, 3.0)   { } // 3
    Foo(double d) : Foo(0, 'a', d) { } // 4
};

在这个例子中,第一个构造函数接受三个参数,并包含一些其他的初始化逻辑,可以认为这是一个最为通用的构造函数。以此为“基准”,后面又定义了三个构造函数,它们又调用了“基准”的构造函数,这样的构造函数就是 C++11 中的委派构造函数。

委派构造函数是在原本初始化列表的位置(冒号后面),调用了另外一个构造函数,以达到委派一部分初始化任务的目的,有时也称为委托构造函数。就像上面的例子一样,在使用委派构造函数时,一般需要先定义一个比较通用的构造函数作为基准,然后其他的构造函数就可以通过这个基准进行委派。委派构造函数也可以包含其它一些逻辑,这些代码会在被委派的“基准”构造函数之后再执行。

注意,在使用委派构造函数时,不允许再同时使用构造函数中的初始化列表。例如下面的例子就是错误的:

struct Foo
{
    Foo(int i, char c, double d) { /*其它一些初始化的逻辑*/ }
    Foo(int i) : Foo(i, 'a', 2.0), name("good") { } // Error,委派构造和初始化列表不可同时使用
    std::string name;
};
使用 Hugo 构建
主题 StackJimmy 设计