为什么 C++ 模板 class 可以访问其基类 class 的私有成员?
Why can a C++ template class access a private member of its base class?
使用 Visual Studio 2019 (v 16.7.3),我发现模板 class 可以访问其非模板基 class 的私有成员。这是预期的行为还是编译器错误?
当派生 class 不是模板 class 时,基础 class 的私有成员无法按预期访问:
class A
{
};
class Base
{
public:
Base() : m_pA(new A()) {}
private:
A* m_pA;
};
class Derived :
public Base
{
public:
Derived() : Base() {}
A* get_a() { return m_pA; } // 'Base::m_pA': cannot access private member declared in class 'Base'
};
int main()
{
Derived d;
}
== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==
但是,如果我将 Derived 设为模板 class,它会毫无怨言地编译:
template<class T>
class Derived :
public Base
{
public:
Derived() : Base() {}
A* get_a() { return m_pA; }
};
int main()
{
Derived<int> d;
}
== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==
贴出的代码没有调用Derived<int>::get_a()
,所以模板成员函数没有被实例化(也没有私有父成员的“access”说话的).
添加以下 get_a()
调用会导致与非模板情况下相同的错误。
{
Derived<int> d;
d.get_a(); // error: 'A* Base::m_pA' is private within this context
}
[ EDIT ] 模板成员函数的隐式实例化(仅)使用时 在 [=23] 下的 C++ 标准中有规定=]:
Unless a member of a templated class is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program;
模板特化的显式定义(虽然不仅仅是声明)将导致所有成员的完整实例化,导致相同的错误。
extern template class Derived<int>; // explicit declaration - ok
template class Derived<int>; // explicit definition - error: 'A* Base::m_pA' is private within this context
[ EDIT #2 ] 在评论(感谢 @Jarod42)中提出了给定代码属于“ill- formed, no diagnostic required”类别,这意味着允许编译器(虽然不是必需的)拒绝它,理由是 class 模板 Derived<T>
不可能(完整)实例化存在因为 get_a()
中的 private
成员访问,即使从未使用过成员函数。
此解释基于 temp.res.general/6.1 的以下部分:
The program is ill-formed, no diagnostic required, if:
- no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or [...]
如果“valid specialization can be generated”表示整个模板的完整explicit特化class,则给定的代码片段确实是“格式错误的 NDR”。
但是,如果“有效特化”被理解为涵盖模板 class 的隐式特化,(仅)具有必需的成员作为在前面引用的 temp.inst/4 中定义,则给定的代码片段不属于“ 格式错误的 NDR”,因为 get_a()
从未实例化(根据 temp.inst/11: "一个实现 不应隐式实例化 一个函数模板,一个变量模板,一个成员模板, 一个非虚成员函数, 成员 class 或模板化 class 的静态数据成员,或 constexpr if 语句的子语句, unless需要这样的实例化").
链接的demo表明gcc
采用后一种解释,而clang
采用前一种解释。
在任何一种情况下,答案都是如果模板没有因为“格式错误的 NDR”而被预先拒绝,因为 no-valid-specializations-can-exist 子句, 然后它将编译并正常工作 只要 get_a()
没有被实例化,无论是通过直接引用隐式地,还是通过模板的显式特化 class .
使用 Visual Studio 2019 (v 16.7.3),我发现模板 class 可以访问其非模板基 class 的私有成员。这是预期的行为还是编译器错误?
当派生 class 不是模板 class 时,基础 class 的私有成员无法按预期访问:
class A
{
};
class Base
{
public:
Base() : m_pA(new A()) {}
private:
A* m_pA;
};
class Derived :
public Base
{
public:
Derived() : Base() {}
A* get_a() { return m_pA; } // 'Base::m_pA': cannot access private member declared in class 'Base'
};
int main()
{
Derived d;
}
== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==
但是,如果我将 Derived 设为模板 class,它会毫无怨言地编译:
template<class T>
class Derived :
public Base
{
public:
Derived() : Base() {}
A* get_a() { return m_pA; }
};
int main()
{
Derived<int> d;
}
== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==
贴出的代码没有调用Derived<int>::get_a()
,所以模板成员函数没有被实例化(也没有私有父成员的“access”说话的).
添加以下 get_a()
调用会导致与非模板情况下相同的错误。
{
Derived<int> d;
d.get_a(); // error: 'A* Base::m_pA' is private within this context
}
[ EDIT ] 模板成员函数的隐式实例化(仅)使用时 在 [=23] 下的 C++ 标准中有规定=]:
Unless a member of a templated class is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program;
模板特化的显式定义(虽然不仅仅是声明)将导致所有成员的完整实例化,导致相同的错误。
extern template class Derived<int>; // explicit declaration - ok
template class Derived<int>; // explicit definition - error: 'A* Base::m_pA' is private within this context
[ EDIT #2 ] 在评论(感谢 @Jarod42)中提出了给定代码属于“ill- formed, no diagnostic required”类别,这意味着允许编译器(虽然不是必需的)拒绝它,理由是 class 模板 Derived<T>
不可能(完整)实例化存在因为 get_a()
中的 private
成员访问,即使从未使用过成员函数。
此解释基于 temp.res.general/6.1 的以下部分:
The program is ill-formed, no diagnostic required, if:
- no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or [...]
如果“valid specialization can be generated”表示整个模板的完整explicit特化class,则给定的代码片段确实是“格式错误的 NDR”。
但是,如果“有效特化”被理解为涵盖模板 class 的隐式特化,(仅)具有必需的成员作为在前面引用的 temp.inst/4 中定义,则给定的代码片段不属于“ 格式错误的 NDR”,因为
get_a()
从未实例化(根据 temp.inst/11: "一个实现 不应隐式实例化 一个函数模板,一个变量模板,一个成员模板, 一个非虚成员函数, 成员 class 或模板化 class 的静态数据成员,或 constexpr if 语句的子语句, unless需要这样的实例化").
链接的demo表明gcc
采用后一种解释,而clang
采用前一种解释。
在任何一种情况下,答案都是如果模板没有因为“格式错误的 NDR”而被预先拒绝,因为 no-valid-specializations-can-exist 子句, 然后它将编译并正常工作 只要 get_a()
没有被实例化,无论是通过直接引用隐式地,还是通过模板的显式特化 class .