使用构造函数的声明会损害访问说明符并且与其他类型的成员不一致

问题描述

今天我了解到一个令人震惊的新现实,所有流行的编译器(我可以在 godbolt.org 上使用的编译器)都对这段代码很好(它可以编译),我无法解释为什么:

class A
{
protected:
    A()
    { }
};

class B : private A
{
    using A::A;
};

int main()
{
    auto b = B{};
    return 0;
}

view on godbolt.org

我的推理:它应该在 auto b = B{}; 处失败,因为 using 声明在私有范围内,因此这是编译器隐式提供的构造函数的位置,作为 using ,该走了。

无论是任何其他成员:函数还是变量,都将根据 using 声明的放置位置 (public/protected/{{1} } 部分).

但是,现在,这不能编译:

private

view on godbolt.org

这是可预测且直观的:

class A
{
protected:
    A(int)
    { }
};

class B : private A
{
    using A::A;
};

int main()
{
    auto b = B{1};
    return 0;
}

但是,不幸的是,由于其他原因,它无法编译(我认为这是直观的),因为这也不是:

<source>:15:14: error: calling a protected constructor of class 'A'
    auto b = B{1};
             ^
<source>:4:5: note: declared protected here
    A(int)

view on godbolt.org


似乎 class A { protected: A(int) { } }; class B : public A { public: using A::A; }; int main() { auto b = B{1}; return 0; } 声明要么措辞不当,要么难以理解。不幸的是,许多编译器(幸运的是,其中一些编译器不再存在于 HEAD 中)也难以解决 using 权限:

friend

view on godbolt.org

一些语言律师可以分析一下并阐明这一点吗?我的假设有误吗?应该是这样吗?

解决方法

class.default.ctor

如果类 X 没有用户声明的构造函数,则没有参数的非显式构造函数被隐式声明为默认值

B 没有用户声明的构造函数。 B 继承自 A 的构造函数不是 B 的构造函数,而是 A 的构造函数。为派生类查找构造函数时会考虑继承的构造函数,但它们仍然不是派生类的构造函数。

标准从未明确说明继承构造函数是否会为派生类创建类似的构造函数。标准确实说基类的构造函数可用于查找和重载解析就好像它们是派生类的构造函数。这个 IMO 意味着它们不被视为派生类的构造函数,尽管如果标准明确说明会更好。无论如何,编译器似乎是这样解释的。

编辑 这是一个 change from C++14,其中继承的构造函数被注入到派生类中。即使在 C+14 中,这些构造函数也是隐式声明而不是用户声明的。

因此 B 有一个 public 隐式声明为默认的默认构造函数,无论它从 A 继承什么。

namespace.udecl

如果基类构造函数在用于构造基类的对象时可访问,则由于使用声明符而考虑的基类构造函数是可访问的;忽略 using 声明的可访问性。

因此 A::A(int) 在构造 B 时是不可访问的,即使导入它的 using 声明是可访问的。

,

using 语句的问题,如果它只导入构造函数的声明,但不会改变它们在公共、受保护、私有意义上的可见性 .

更健壮的方法是声明一个公共 ctor:

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

这样你就可以使用来自明确声明的公共成员的受保护成员。


参考:

C++20 的 n4860 草案在 9.9 中说使用声明 [namespace.udecl] §1

...如果 using-declarator 没有命名构造函数,则 unqualified-id 是 在声明区域中声明,其中 using 声明作为每个声明的同义词出现 由 using 声明符引入。 如果 using-declarator 命名了一个构造函数,则它声明该类继承了构造函数的集合 由来自指定基类的 using 声明符引入的声明

只有非构造函数才会以这种方式被声明为 public。

,

原因是编译器会在类中没有的情况下添加一个默认的公共非参数构造函数。编译后你的第一个案例将是这样的:

class A
{
protected:
    A()
    { }
};

class B : private A
{
    using A::A;
public:
    B() : A() {}
};

int main()
{
    auto b = B{};
    return 0;
}