我试图找出失败的原因。我正在使用的代码基本上压缩到下面的代码。我有一个简单的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;
}
答案 0 :(得分:8)
当MSVC实例化一个类时,它还会填充其vtable,并为此目的实例化其所有虚函数,即使是那些从未调用过的虚函数。
在您的情况下,如果没有编译器看到A的完整定义,则无法实例化函数useX
。
如果将useX声明为非虚拟,则MSVC可以正常工作。
似乎这种行为依赖于编译器;例如,AIX在实例化(未使用)函数时比MSVC更具攻击性。
答案 1 :(得分:4)
首先,正如许多人所说,标准完全允许编译器在这种情况下实例化(或选择不实例化)他们的内容;通过严格解释标准,这是代码问题,而不是MSVC错误。您依赖的行为是特定于平台的,因此是非标准的;虽然这本身并不是一个“错误”,但它显然是不可移植的。
然而,理解为什么GCC和Clang与MSVC有所不同,这与在每个编译器中如何设置vtable有关。
快速概述对象在内存中的外观:
在第一种情况下,所有信息都可以是静态的;成员函数只是隐藏的正常函数。在第二种情况下,派生类(或类,对于长继承列表)可以巧妙地放在内存中的基类之后,以便Derived * == Base *。显然,这种优化不适用于多重继承,这意味着每次调用都需要调整this指针。然后,虚拟继承添加一个数组vtable,它在运行时动态选择正确的函数。
这与未使用成员的实例化有什么关系?
一般而言,无论您的实施方式如何,您都需要三件事:
G ++有一个标准化且复制良好的方式来进行这些调整,在许多编译器中使用(包括Clang,因为Clang意味着替代GCC):
这是一种奇怪的缓存优化,它有一些好处;它被广泛复制,尽管不是最优雅的解决方案。它对所有情况使用一种结构,并且每次执行相同的计算,具体取决于编译器优化以在适用时用直接调用替换虚拟表跳(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。