使用构造函数声明会损害访问说明符并且与其他类型的成员不一致
Using declaration of constructors compromises access specifiers and isn't consistent with other types of members
今天我了解到一个新的令人震惊的现实,所有流行的编译器(我可以动手的编译器,在 godbolt.org 上)都可以很好地处理这段代码(编译),我无法解释为什么:
class A
{
protected:
A()
{ }
};
class B : private A
{
using A::A;
};
int main()
{
auto b = B{};
return 0;
}
我的推理:它应该在 auto b = B{};
处失败,因为 using
声明在私有范围内,因此这是编译器隐式提供的构造函数所在的位置,作为 [=16] 的结果=], 应该去。
无论是任何其他成员:函数还是变量,它的访问修饰符都将根据 using
声明的放置位置来确定(public
/protected
/private
节)。
但是,现在,这无法编译:
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)
但是,不幸的是,由于其他原因,它没有编译(我相信这是直观的),因为这也不是:
class A
{
protected:
A(int)
{ }
};
class B : public A
{
public:
using A::A;
};
int main()
{
auto b = B{1};
return 0;
}
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;
}
有语言律师能分析一下吗?我的假设错了吗?这应该是这样的?
如果 using
语句仅 导入 构造函数的声明但不会更改它们的 visibility,则存在问题在某种意义上 public,受保护的,私有的。
更稳健的方法是声明一个 public ctor:
class B : public A
{
public:
B(int i): A(i){};
};
这样您就可以使用来自显式声明的 public 成员的受保护成员。
参考:
C++20 的 n4860 草案在 9.9 中使用声明 [namespace.udecl] §1
...If the using-declarator does not name a constructor, the unqualified-id is
declared in the declarative region in which the using-declaration appears as a synonym for each declaration
introduced by the using-declarator. If the using-declarator names a constructor, it declares that the class inherits the set of constructor
declarations introduced by the using-declarator from the nominated base class.
那样只会声明非构造函数public。
原因是如果class中没有,编译器会添加一个默认的public无参构造函数。你的第一个case编译后是这样的:
class A
{
protected:
A()
{ }
};
class B : private A
{
using A::A;
public:
B() : A() {}
};
int main()
{
auto b = B{};
return 0;
}
If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted
classB
没有用户声明的构造函数。 B
继承自A
的构造函数不是B
的构造函数,而是A
的构造函数。在查找派生 class 的构造函数时会考虑继承构造函数,但它们仍然不是派生 class.
的构造函数
标准从未明确表示继承构造函数会或不会为派生 class 创建类似的构造函数。该标准确实表示基class 的构造函数可用于查找和重载解析,就好像 它们是派生class 的构造函数一样。这个 IMO 意味着它们不被视为派生 class 的构造函数,但如果标准明确说明会更好。无论如何,编译器似乎是这样解释的。
编辑 这是一个 change from C++14,其中继承的构造函数被注入派生的 class。即使在 C+14 中,这些构造函数也是隐式声明而不是用户声明的。
因此 B
有一个 public 隐式声明为默认的默认构造函数,无论它从 A
.[=25 继承什么=]
Base-class constructors considered because of a using-declarator are accessible if they would be accessible when used to construct an object of the base class; the accessibility of the using-declaration is ignored.
因此 A::A(int)
在构造 B
时不可访问,即使导入它的 using
声明是可访问的。
今天我了解到一个新的令人震惊的现实,所有流行的编译器(我可以动手的编译器,在 godbolt.org 上)都可以很好地处理这段代码(编译),我无法解释为什么:
class A
{
protected:
A()
{ }
};
class B : private A
{
using A::A;
};
int main()
{
auto b = B{};
return 0;
}
我的推理:它应该在 auto b = B{};
处失败,因为 using
声明在私有范围内,因此这是编译器隐式提供的构造函数所在的位置,作为 [=16] 的结果=], 应该去。
无论是任何其他成员:函数还是变量,它的访问修饰符都将根据 using
声明的放置位置来确定(public
/protected
/private
节)。
但是,现在,这无法编译:
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)
但是,不幸的是,由于其他原因,它没有编译(我相信这是直观的),因为这也不是:
class A
{
protected:
A(int)
{ }
};
class B : public A
{
public:
using A::A;
};
int main()
{
auto b = B{1};
return 0;
}
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;
}
有语言律师能分析一下吗?我的假设错了吗?这应该是这样的?
如果 using
语句仅 导入 构造函数的声明但不会更改它们的 visibility,则存在问题在某种意义上 public,受保护的,私有的。
更稳健的方法是声明一个 public ctor:
class B : public A
{
public:
B(int i): A(i){};
};
这样您就可以使用来自显式声明的 public 成员的受保护成员。
参考:
C++20 的 n4860 草案在 9.9 中使用声明 [namespace.udecl] §1
...If the using-declarator does not name a constructor, the unqualified-id is declared in the declarative region in which the using-declaration appears as a synonym for each declaration introduced by the using-declarator. If the using-declarator names a constructor, it declares that the class inherits the set of constructor declarations introduced by the using-declarator from the nominated base class.
那样只会声明非构造函数public。
原因是如果class中没有,编译器会添加一个默认的public无参构造函数。你的第一个case编译后是这样的:
class A
{
protected:
A()
{ }
};
class B : private A
{
using A::A;
public:
B() : A() {}
};
int main()
{
auto b = B{};
return 0;
}
If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted
classB
没有用户声明的构造函数。 B
继承自A
的构造函数不是B
的构造函数,而是A
的构造函数。在查找派生 class 的构造函数时会考虑继承构造函数,但它们仍然不是派生 class.
标准从未明确表示继承构造函数会或不会为派生 class 创建类似的构造函数。该标准确实表示基class 的构造函数可用于查找和重载解析,就好像 它们是派生class 的构造函数一样。这个 IMO 意味着它们不被视为派生 class 的构造函数,但如果标准明确说明会更好。无论如何,编译器似乎是这样解释的。
编辑 这是一个 change from C++14,其中继承的构造函数被注入派生的 class。即使在 C+14 中,这些构造函数也是隐式声明而不是用户声明的。
因此 B
有一个 public 隐式声明为默认的默认构造函数,无论它从 A
.[=25 继承什么=]
Base-class constructors considered because of a using-declarator are accessible if they would be accessible when used to construct an object of the base class; the accessibility of the using-declaration is ignored.
因此 A::A(int)
在构造 B
时不可访问,即使导入它的 using
声明是可访问的。