在模板基类中的虚函数中使用前向声明的类,其中构造函数只需要前向声明?

时间:2013-12-18 22:04:40

标签: c++ templates visual-c++ gcc

我试图找出失败的原因。我正在使用的代码基本上压缩到下面的代码。我有一个简单的A类,我专门用一个模板。模板不需要这种类型来编译它的构造函数,并且我实际调用的构造函数(派生类型)没有公开,因此编译器此时无法为构造函数生成代码。

GCC和Clang没有。然而,MSVC(2008 + 2010)确实尝试编译虚拟成员,因此不编译。

这是GCC和Clang,还是MSVC的错误?或者我是否会进入UB领域?

class A;

template <typename X>
class S {
public:
    S() {}

    virtual int useX() { return X::value; }
};

class T : public S<A> {
public:
    T();
};

int main()
{
    new T();
    return 0;
}

3 个答案:

答案 0 :(得分:8)

当MSVC实例化一个类时,它还会填充其vtable,并为此目的实例化其所有虚函数,即使是那些从未调用过的虚函数。

在您的情况下,如果没有编译器看到A的完整定义,则无法实例化函数useX

如果将useX声明为非虚拟,则MSVC可以正常工作。

似乎这种行为依赖于编译器;例如,AIX在实例化(未使用)函数时比MSVC更具攻击性。

答案 1 :(得分:4)

首先,正如许多人所说,标准完全允许编译器在这种情况下实例化(或选择不实例化)他们的内容;通过严格解释标准,这是代码问题,而不是MSVC错误。您依赖的行为是特定于平台的,因此是非标准的;虽然这本身并不是一个“错误”,但它显然是不可移植的。

然而,理解为什么GCC和Clang与MSVC有所不同,这与在每个编译器中如何设置vtable有关。

快速概述对象在内存中的外观:

  1. 没有继承,所有通话都是静态的,没有vtable或调整
  2. 单一继承,所有呼叫都是静态的,聪明的组织
  3. 多重继承,所有调用都是静态的,指针调整
  4. 虚拟继承,调用可以是动态的,vtable和指针调整
  5. 在第一种情况下,所有信息都可以是静态的;成员函数只是隐藏的正常函数。在第二种情况下,派生类(或类,对于长继承列表)可以巧妙地放在内存中的基类之后,以便Derived * == Base *。显然,这种优化不适用于多重继承,这意味着每次调用都需要调整this指针。然后,虚拟继承添加一个数组vtable,它在运行时动态选择正确的函数。

    这与未使用成员的实例化有什么关系?

    一般而言,无论您的实施方式如何,您都需要三件事:

    1. 一个delta,详细说明了你所关心的子对象在哪里
    2. 虚拟索引(vindex),详细说明要调用的虚拟方法
    3. 要调用的方法数组(vtable)
    4. G ++有一个标准化且复制良好的方式来进行这些调整,在许多编译器中使用(包括Clang,因为Clang意味着替代GCC):

      1. 明确存储增量
      2. 在vtable中存储vindex和偏移量计算,方法地址在evens和vindex上的赔率
      3. 这是一种奇怪的缓存优化,它有一些好处;它被广泛复制,尽管不是最优雅的解决方案。它对所有情况使用一种结构,并且每次执行相同的计算,具体取决于编译器优化以在适用时用直接调用替换虚拟表跳(GCC优化器非常出色的任务)。

        另一方面,MSVC不仅仅是有点不优雅。这是一个可怕的黑客:它为每个案例使用不同的结构。这允许它避免不必要的计算的开销,并且在许多情况下节省空间。但是,这意味着转换成员函数指针会导致它们更改大小,在常见情况下(派生到基础)导致它们丢失信息

        因此,与更优雅的GCC实现不同,MSVC绝对必须实例化虚拟成员函数;如果没有,它可能很容易丢失翻译单位之间或链接期间的信息,并具有由不同大小的结构表示的相同对象

        这是一个问题,他们实际上只是为处理它而添加了关键字:http://msdn.microsoft.com/en-us/library/ck561bfk.aspx

        因此,虽然您认为编译器不需要该类型,但 MSVC肯定会这样做,否则它几乎肯定会不安全。

        编辑:将自己与简单的单一继承混淆,基类变为第一:

        class A : public B
        

        变为

        [[B] A]

        所以指针* B == * A

答案 2 :(得分:3)

从草案C ++ 1y标准:(通过@DyP)

  

实现不得隐式实例化函数模板,变量模板,成员tem-   plate,非虚方法成员函数,成员类或类模板的静态数据成员   不需要实例化。 未指定实现是否隐式实例化   如果虚拟成员函数不在其中,则为类模板的虚拟成员函数   stantiated。在默认参数中使用模板特化不应导致模板   隐式实例化,除了可以在需要完整类型的情况下实例化类模板   确定默认参数的正确性。在函数调用中使用默认参数会导致   默认参数中的特化要隐式实例化。

强调我的。 virtual中的任何template函数都可以在任何时候由任何C ++编译器实例化,而不会违反C ++标准。因此,MSVC是符合此事项的标准。

这是the current draft N3797中的14.7.1.11。