C ++入门(第5版)声明:
- 如果基类构造函数具有默认参数,则不会继承这些参数。相反,派生类获取多个继承的构造函数,其中每个参数都连续省略。
这条规则背后的原因是什么?
答案 0 :(得分:9)
鉴于目前的措辞;我认为这些术语(C++ WD n4527的§12.9/ 1)中有详细说明(但主要是为了避免潜在的歧义);
inheriting constructors是一种类似于代码生成的技术(“我想要我的基础”)。没有办法指定你得到的构造函数,你基本上都得到它们,因此编译器非常注意不要生成不明确的构造函数。
举例来说;
#include <iostream>
using namespace std;
struct Base {
Base (int a = 0, int b = 1) { cout << "Base" << a << b << endl; }
};
struct Derived : Base {
// This would be ambiguous if the inherited constructor was Derived(int=0,int=1)
Derived(int c) { cout << "Derived" << c << endl; }
using Base::Base;
};
int main()
{
Derived d1(3);
Derived d2(4,5);
}
输出;
Base01
Derived3
Base45
有一个提案n4429(如Jonathan Wakely所述)关于继承构造函数和类的使用声明的措辞的更改。
鉴于该提案的意图;
...这个提议使继承构造函数就像继承任何其他基类成员一样。
有以下变化(新措辞);
更改7.3.3 namespace.udecl第15段:
当 using-declaration 将基类的声明带入派生类时......这些隐藏或重写的声明将从using声明引入的声明集中排除。
然后立即使用一个直接处理构造函数的示例(尽管没有默认参数);
struct B1 {
B1(int);
};
struct B2 {
B2(int);
};
struct D1 : B1, B2 {
using B1::B1;
using B2::B2;
};
D1 d1(0); // ill-formed: ambiguous
struct D2 : B1, B2 {
using B1::B1;
using B2::B2;
D2(int); // OK: D2::D2(int) hides B1::B1(int) and B2::B2(int)
};
D2 d2(0); // calls D2::D2(int)
简而言之,虽然可能不是最终的措辞,但似乎意图是允许构造函数与其默认参数一起使用并明确排除隐藏和重写的声明,因此我相信会处理任何歧义。措辞确实似乎简化了标准,但产生了相同的结果w.r.t.它被用在客户端代码中。
答案 1 :(得分:6)
默认参数不是函数签名的一部分,可以在以后添加,也可以在限制范围内添加,这将无法更改派生类的已定义构造函数,例如。
// in A.h
struct A {
A(int, int);
};
// in B.h
#include "A.h"
struct B : A {
using A::A;
};
// in A.cc
#include "A.h"
A::A(int, int = 0) { }
在文件A.cc
中,您可以使用单个参数构造A
,因为默认参数是可见的,但是当声明B
时,默认参数不可见,所以不能在继承构造函数时要考虑。我相信这是默认参数得到特殊处理的一个原因。
虽然显然继承构造函数的工作方式可能会发生变化,而默认参数不会得到这种特殊处理,但请参阅http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4429.html
答案 2 :(得分:2)
这条规则背后的原因是什么?
它可以防止来自基类的默认参数的更改影响所有派生类(在派生类范围内)的行为,这将是派生类创建者的意外。
答案 3 :(得分:1)
正如Jonathan Wakely所提到的那样, C ++ 17已经改变了这种行为。现在,参数列表中的默认参数将被继承。
也就是说,如果我们在名为Base
的类中有以下构造函数,
struct Base {
Base(int a, int b, int c = 1, int d = 2, int e = 3) {}
};
然后对于上面的构造函数,这些是在C ++ 11 / C ++ 14中被“注入”派生类的相应构造函数:
struct Derived : Base {
using Base::Base;
/*
C++11/C++14:
Derived::Derived(int a, int b) : Base(a, b) {}
Derived::Derived(int a, int b, int c) : Base(a, b, c) {}
Derived::Derived(int a, int b, int c, int d) : Base(a, b, c, d) {}
Derived::Derived(int a, int b, int c, int d, int e) : Base(a, b, c, d, e) {}
*/
};
而C ++ 17中的那个现在变得更加简单:
struct Derived : Base {
using Base::Base;
/*
C++17:
Derived::Derived(int a, int b, int c = 1, int d = 2, int e = 3) : Base(a, b, c, d, e) {}
*/
};
基于继承构造函数的cppreference.com page和引入更改的文章(P0136R1),整个[class.inhctor]\1
子节指定了如何将继承的构造函数拆分并“注入”到派生类已被删除。 (事实上整个[class.inhctor]
部分已删除)。然后用C ++ 17中的[namespace.udecl]\16
中的一个简单规则替换它(强调我的):
出于重载解析的目的,将using声明引入派生的函数 class被视为它们是派生类的成员。特别是隐含的这个参数 应被视为指向派生类而不是基类的指针。这没有影响 函数的类型,在所有其他方面,函数仍然是基类的成员。同样, 由using声明引入的构造函数被视为它们的构造函数 查找派生类(6.4.3.1)的构造函数或形成一组重载时的派生类 候选人(16.3.1.3,16.3.1.4,16.3.1.7)。如果选择这样的构造函数来执行初始化 类型的对象,除构造函数所源自的基类之外的所有子对象 隐式初始化(15.6.3)。
所以参数列表现在'完全继承'。事实上,这是我使用符合P0136R1标准的CLion和GCC 7.2的经验,而我的非P0136R1兼容的Visual Studio 2017(15.6)显示了较旧的4个构造函数,默认参数被删除。