clang的-Wweak-vtables是什么意思?


78

我基本上不了解clang -Wweak-vtables。到目前为止,这是我观察到的:

情况一:(触发警告)

class A {
    public:
    virtual ~A(){}        
};

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

情况二:(不触发警告)

class A {
    public:
    virtual ~A(){}        
};   

int main(){}

情况三:(不触发警告)

class A {
    public:
    virtual ~A();

};

A::~A(){}

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

案例四:(触发警告)

class A {
    public:
    virtual ~A(){}
    virtual void fun(){}        
};    

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

情况五:(不触发警告)

class A {
    public:
    virtual ~A(){}
    virtual void fun();      
};    

class B : public A {
    public:
    virtual ~B(){}
};

int main(){}

情况六:(不触发警告)

class A {
    public:
    virtual ~A(){}
    virtual void fun(){}
};    

class B : public A {};

int main(){}

情况七:(不触发警告)

class A {
    public:
    virtual ~A(){}
    virtual void fun(){}
};    

class B : public A {
    public:
    virtual void fun(){}
};

int main(){}

确切的警告是

warning: 'A' has no out-of-line virtual method definitions; its vtable 
will be emitted in every translation unit [-Wweak-vtables]

因此,显然,如果我没有在类中声明非内联虚拟函数,则当且仅当我从该类派生并且派生类具有虚拟析构函数时,它才会引起某种问题。

问题:

  1. 为什么这是个问题?
  2. 为什么通过声明虚函数来解决此问题?(警告谈到定义)
  3. 如果我不是从班级派生的,为什么不会出现警告?
  4. 当派生类没有虚拟析构函数时,为什么不会出现警告?

Answers:


102

如果类的所有virtual方法都是内联的,则编译器无法选择在其中放置vtable单个共享副本的转换单元-而是必须将vtable的副本放置在需要它的每个目标文件中。在许多平台上,链接器都可以通过丢弃重复的定义或将所有引用映射到一个副本来统一这些多个副本,因此这仅是警告。

实现一个virtual功能外的线,编译器可以选择翻译单元,实现了这一点的行方法为“家”类的实现细节,以及地方在同一个翻译单元的虚函数表的单个共享副本。如果有多个方法,则编译器可以选择任意方法,只要该选择仅由类的声明确定即可;例如,GCC按声明顺序选择第一个非内联方法。

如果不重写类的任何方法,则virtual关键字没有可观察到的效果,因此编译器无需为该类发出vtable。如果您不从中派生A,或者如果您未能声明派生类的析构函数virtual,则其中没有重写的方法A,因此A将省略vtable。如果您声明了一个额外的离线virtual方法来抑制警告,并且还执行了一些替代方法A,则virtual需要在链接的转换单元中提供非内联的实现(及其随附的vtable副本)。 ,否则链接将因缺少vtable而失败。


2
当同一类别的(不同)离线方法在多个不同的TU中出现时,该怎么办?
MM

2
@Matt,基于类的声明以某种任意的但确定性的方式选择了一种离线方法,并且vtable最终与方法实现位于同一TU中。所有相关的TU都看到相同的声明:除非声明声明不一致,否则它们将就所选方法达成一致,从而共同发出一个vtable。
Jeffrey Hantin

这个答案不能解释案例6和案例
Leon

2
@JeffreyHantin “ B的析构函数不是虚拟的”是什么意思?编译器生成(隐式声明)的析构函数B应该是虚的(因为已经声明了基类的析构函数virtual)。
里昂

2
@Leon,引用规范:“默认使用但未定义为删除的析构函数在使用odr(3.2)或在其首次声明后显式默认时会隐式定义。” 在6或7的情况下,什么~B()都不会使用或不使用默认值,因此编译器不会隐式实例化默认定义。如果不存在,如何虚拟?这些示例的琐碎性导致了一些违反直觉的效果。
杰弗里·汉汀
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.