【问题标题】:Why can a C++ template class access a private member of its base class?为什么 C++ 模板类可以访问其基类的私有成员?
【发布时间】:2021-06-29 00:02:31
【问题描述】:

使用 Visual Studio 2019 (v 16.7.3),我发现模板类可以访问它的非模板基类的私有成员。这是预期行为还是编译器错误?

当派生类不是模板类时,基类的私有成员按预期是不可访问的:

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 设为模板类,它会毫无怨言地编译:

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 ==

【问题讨论】:

  • 你能在派生类上调用get_a吗
  • Clang 成功找到错误Demo(即使不需要)。

标签: c++ templates inheritance private


【解决方案1】:

发布的代码没有调用Derived&lt;int&gt;::get_a(),因此模板成员函数没有被实例化(并且没有私有父成员的“访问”可言)。

添加以下get_a() 调用会导致与非模板情况相同的错误。

{
    Derived<int> d;
    d.get_a();  // error: 'A* Base::m_pA' is private within this context
}

[ EDIT ] 模板成员函数的隐式实例化(仅)当使用时在temp.inst/4 下的 C++ 标准中规定:

除非模板化类的成员是已声明的特化,否则 成员的特化会在引用特化时隐式实例化 在需要成员定义存在的上下文中,或者如果成员定义的存在影响程序的语义;

模板特化的显式定义(尽管不仅仅是声明)将导致所有成员的完全实例化,从而导致相同的错误。

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 ] 参数是在 cmets 中提出的(感谢 @Jarod42),即给定代码属于“格式错误,不需要诊断 " 类别,意味着允许编译器(尽管不是必需的)拒绝它,理由是由于 get_a() 中的 private 成员访问,类模板 Derived&lt;T&gt; 不可能(完整)实例化不存在如果成员函数从未使用过。

此解释基于temp.res.general/6.1的以下部分:

程序 格式错误,无需诊断,如果:

  • 没有有效的特化 可以生成 模板 或子语句在模板中使用 constexpr if 语句并且模板未实例化,或者 [...]
  • 如果“可以生成有效的特化”表示整个模板类的完整显式特化,那么给定的代码sn-p确实是“格式错误的 NDR”。

  • 但是,如果“有效特化”被理解为涵盖模板类的隐式特化,其中(仅)具有前面引用的temp.inst/4 中定义的必需成员,那么给定的代码 sn- p 不属于“格式错误的 NDR”,因为 get_a() 从未实例化(根据 temp.inst/11:“一个实现 不应隐含实例化 一个函数模板,一个变量模板,一个成员模板, 一个非虚成员函数,模板类的成员类或静态数据成员,或 constexpr if 语句的子语句, 除非需要这种实例化”)。

链接的demo 表明gcc 采用后一种解释,而clang 采用前者。

在任何一种情况下,答案都是如果模板没有因为 no-valid-specializations-can-exist 子句而被预先拒绝为“ill-formed NDR”,那么它将编译并且工作正常只要 get_a() 没有被实例化,无论是通过直接引用隐式还是通过模板类的显式特化。

【讨论】:

  • 对。记住模板是什么很重要——它不是代码,它是用于在填写完模板类型后生成代码的指令。如果没有调用该函数,则不会生成任何代码,因此不会生成错误。
  • 请注意,代码是格式错误的 NDR(因为可能不存在有效的实例化)。成功诊断它:-) Demo
  • @Jarod42 这很有趣。可能是一个警告,但我不明白为什么会出现硬错误。有问题的成员函数永远不会在该 sn-p 中实例化。
  • 硬错误,因为没有U,例如Derived&lt;U&gt;::get_a,格式正确。与static_assert(false); 相同。 m_pA 甚至不依赖,所以这里的检查相对容易。
  • @Jarod42 也许我看错了,但是 AFAICT 在这种情况下不应该检查,因为没有实例化。来自temp.inst/11:“一个实现 不应隐式实例化 一个函数模板、一个变量模板、一个成员模板, i> 非虚拟成员函数,模板类的成员类或静态数据成员,或 constexpr if 语句的子语句, 除非需要这样的实例化".
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-12
  • 2011-01-06
  • 1970-01-01
  • 2017-08-07
  • 2012-07-23
  • 2014-04-02
相关资源
最近更新 更多