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

问题描述

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

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

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

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

在 godbolt.org 上查看

我的推理:它应该在 失败auto b = B{};,因为using声明在私有范围内,因此编译器隐式提供的构造函数using应该去那里。

无论是任何其他成员:函数还是变量,它的访问修饰符都将根据using声明的放置位置(public/ protected/private部分)来确定。

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

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

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

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

在 godbolt.org 上查看

这是可预测和直观的:

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

但是,不幸的是,由于其他原因,它没有编译(我相信这是直观的),因为这也不是:

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

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

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

在 godbolt.org 上查看


似乎using声明要么措辞不当,要么理解不力。不幸的是,许多编译器(其中一些,幸运的是,不再是在 HEAD 中)也在friend权限问题上苦苦挣扎:

class C;

class A
{
    friend class C;

protected:
    A(int)
    { }
};

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

class C
{
public:
    B make_b()
    {
        return B{1};
    }
};

int main()
{
    auto b = C{}.make_b();
    return 0;
}

在 godbolt.org 上查看

一些语言律师可以对此进行分析并提供一些启示吗?我的假设错了吗,这应该是这样吗?

标签: gccvisual-c++clangc++17icc

解决方案


类.default.ctor

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

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

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

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

因此B有一个public隐式声明为默认的默认构造函数,不管它继承自什么A

命名空间.udecl

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

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


推荐阅读