【问题标题】:Template classes vs Private inheritance模板类与私有继承
【发布时间】:2011-10-18 13:25:40
【问题描述】:

为什么有些编译器坚持要求模板基类的合格成员公共成员,而不要求非模板类相同?请看以下代码清单:

模板类:

#include <iostream>

using namespace std;

template <class T>
class TestImpl {
public: // It wont make a difference even if we use a protected access specifier here
    size_t vval_;
    TestImpl(size_t val = 0) : vval_(val) { }
};

template <class T>
class Test : public TestImpl<T> {
public:
    Test(size_t val) : TestImpl<T>(val) {
        cout << "vval_ : " << vval_ << endl; // Error: vval_ was not declared in this scope
        //! cout << "vval_ : " << TestImpl<T>::vval_ << endl; // this works, obviously
    }
};

int main() {
    Test<int> test1(7);

    return 0;
}

非模板类:

#include <iostream>

using namespace std;

class TestImpl {
public: // It wont make a difference even if we use a protected access specifier here
    TestImpl(size_t val = 0) : vval_(val) {}
    size_t vval_;
};

class Test : public TestImpl {
public:
    Test(size_t val) : TestImpl(val) {
        cout << "vval_ : " << vval_ << endl;
    }
};

int main() {
    Test test1(7);

    return 0;
}

上述代码清单之间的显着区别在于,第一个清单使用模板类,而第二个不使用。

现在,两个列表都可以使用 Microsoft 的 Visual Studio 编译器 (cl) 编译,但第一个列表 WONT 可以同时使用 Digital Mars 编译器 (dmc) 和 Minimalist GNU for Windows (MinGW - g++) 编译器。我会收到类似“vval_ was not declared in the scope”之类的错误——我显然明白这是什么意思。

如果我有资格使用 TestImpl::vval_ 访问 TestImpl 的公共变量 vval_,则代码有效。在第二个清单中,当派生类访问基类的 vval_ 变量而不对其进行限定时,编译器不会报错。

关于这两个编译器和可能的其他编译器,我的问题是为什么我应该能够直接从继承自的 非模板类直接访问(不限定)vval_ 变量一个非模板类,而我不能从从模板类继承的模板类做同样的事情?

【问题讨论】:

  • 您也可以限定vval_,因此:this-&gt;vval_
  • @Rob: this->vval_ 在派生类中工作。当使用“以一种方式实现”的继承时,您的实现的重要部分在基本 claas 中,感觉必须在每个地方都这样做很笨拙。谢谢
  • @visitor: 揭示阅读
  • @JohnGathogo:如果标识符依赖于类型参数,但该依赖关系在代码中不明确,您将需要明确声明该依赖关系,this-&gt; 将开始看起来不那么糟糕...

标签: c++ oop inheritance


【解决方案1】:

您必须使用TestImpl&lt;T&gt; 限定vval_,以告诉编译器它取决于Test&lt;T&gt;T 的实际类型(在Test&lt;T&gt; 的定义,它的实例化将改变 vval_ 在该上下文中的含义。为了让编译器意识到这一点,你必须告诉 vval_ 是(模板参数)依赖的。

另见http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html

【讨论】:

    【解决方案2】:

    MSVC(Microsoft ...)在模板代码方面从来都不是标准兼容的,所以他是个奇怪的人:)

    问题是模板是分两个阶段解析的:

    • 第一阶段在模板解析时执行,任何不依赖(显式)模板参数的标识符都应该被解析
    • 第二阶段在模板实例化时执行

    在您的情况下,第一阶段失败,因为 vval_ 没有明确依赖于模板参数(不是依赖名称),因此它应该可用。

    一个简单的补救措施是限定vval_,通常使用this-&gt;,将其标记为依赖。

    【讨论】:

    • 实际上并没有那么糟糕,唯一缺少的东西(标准要求)是两阶段名称查找。并且不需要是两阶段解析,例如可以想象编译器实现只会记住模板定义时的上下文,并在实例化时将其用于正确的名称查找。
    • @KonstantinOznobihin:我不同意没那么糟糕。名称查找类似于重载解析:对于总是选择最后一个可能的重载的编译器会说什么?我知道他们可能不希望用依赖名称问题来纠缠用户的可用性问题,但您可以这样做而不会破坏查找:x
    • @KonstantinOznobihin:嗯...标准确定模板上下文中的依赖类型必须以typename 关键字(或带有template 关键字的依赖模板)开头,VS 忽略该要求,使得编写不可移植的代码变得相当容易......这一切都取决于你如何看待它
    • 不过,我认为,缺少两阶段名称查找几乎不能被视为“在模板代码方面从未符合标准”。当我们进行名称查找时,您是否有任何依赖于执行两阶段名称查找的好代码示例?确实缺少此功能并不是很好,但是您与重载解决方案的类比是不正确的。
    • @KonstantinOznobihin:考虑:int x = 10; template &lt;typename T&gt; struct base { static int x; }; template &lt;typename T&gt; int base&lt;T&gt;::x = 1; template &lt;typename T&gt; struct derived : base&lt;T&gt; { void foo() { std::cout &lt;&lt; x; } 我相信根据标准(我必须检查),derived&lt;T&gt;::foo() 中的x 指的是::x 而不是base&lt;T&gt;::x。当然,我从未在实际代码中发现过这个问题。
    【解决方案3】:

    您面临的问题是,对于编译器而言,vval_ 不是依赖名称,因此它会在使用该类型实际实例化模板之前尝试查找它。那时,编译器 [*] 还不知道基类型,因此它不考虑模板化的基。 Visual Studio 不执行两阶段查找,因此这里不需要。

    解决方案是将标识符转换为依赖标识符,这可以通过多种方式之一完成。最简单且推荐的方法是使用this(如this-&gt;vval_)。通过添加显式this,编译器知道vval_ 可以根据模板参数而有所不同,它现在是一个依赖名称,它会将查找推迟到第二阶段(在参数替换之后) .

    或者,您可以按照@mrozenau 的建议,通过使用TestImpl&lt;T&gt;::vval_ 来限定标识符所属的类型。这再次使标识符依赖于模板参数T 并且查找被推迟。虽然它们都服务于将查找推迟到以后的最终目的,但第二种方法具有额外的副作用,即动态调度将被禁用。在这种特殊情况下,这无关紧要,但如果vval_ 实际上是一个虚函数,那么this-&gt;f() 将调用最终覆盖器,而TestImpl&lt;T&gt;::f() 将执行存在于TestImpl&lt;T&gt; 中的覆盖器。

    [*] 在模板的第一阶段验证期间,在将参数代入模板之前,基类型尚不清楚。原因是不同的参数集可能会触发对基本模板的不同特化的选择。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-04
      • 2020-09-04
      • 1970-01-01
      • 1970-01-01
      • 2016-12-30
      • 2018-05-23
      • 2011-01-05
      相关资源
      最近更新 更多