【问题标题】:Clang -Wweak-vtables and pure abstract classClang -Wweak-vtables 和纯抽象类
【发布时间】:2015-05-01 12:56:48
【问题描述】:

关于此主题的先前问题:

这是我最近提出的问题的后续: clang: no out-of-line virtual method definitions (pure abstract C++ class) 并且被标记为这个问题的重复:What is the meaning of clang's -Wweak-vtables?。我不认为这回答了我的问题,所以我在这里专注于让我困惑且尚未得到解答的问题。

我的场景:

我正在尝试使用 Clang-3.5 编译以下简单的 C++ 代码:

test.h:

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

test.cc

#include "test.h"

A::A() {;}
A::~A() {;}

我用来编译这个的命令(Linux,uname -r: 3.16.0-4-amd64):

$clang-3.5 -Wweak-vtables -std=c++11 -c test.cc

我得到的错误:

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

当 A 类不是纯抽象时,上面的代码构建得很好。以下代码不会发出警告,唯一的变化是类 A 不再是抽象的:

test2.h:

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

test2.cc

#include "test2.h"

A::A() {;}
A::~A() {;}

我的问题

纯抽象类有什么特别之处,以至于上面的代码会在 Clang 中触发警告?

【问题讨论】:

  • 在您的第二个示例中,A 根本不是抽象的
  • 如果您没有定义A::~A,您认为会发生什么?然后,如果编译器等待看到A::~A 的定义来发出vtable,那么就没有vtable!
  • @immibis 这是正确的行为,不是吗? (包括test2.h在内的其他TU也会得出同样的结论)

标签: c++ clang abstract-class clang++ llvm-clang


【解决方案1】:

具有虚方法的类总是需要发出一个 vtable。编译器需要指示 vtable 的存储位置 - 通常在实现其第一个函数的对象中。

纯抽象类有什么特别之处?由于它们没有方法,编译器必须在每个翻译单元中输出一个 vtable,以便每个翻译单元可以引用纯抽象基类型。这就是警告告诉你的。

您可能会关心,例如,如果您想避免在内存非常低的环境中复制该内存,或者如果您查看对象并想知道为什么在该位置周围有多个 vtable 副本。

无论如何,您可以将多态指针指向A 对象这一事实意味着编译器必须发出一些关于该类型的信息——vtable。

方案一:实现虚方法,比如析构函数

在创建抽象基类时,我的偏好是提供外联的虚拟析构函数; IE。在 .cpp 文件中实现A::~A()。用户声明的虚拟析构函数的缺点是它隐式删除了自动生成的复制和移动构造函数和运算符,因此您最终需要重新声明它们。根据rule of five,这会导致这样的基类:

啊哈:

class A {
public:
    A() = default;

    A(const A&) = default;
    A(A&&) = default;
    A& operator=(const A&) = default;
    A& operator=(A&&) = default;
    virtual ~A();

    virtual void doSomething() = 0;
};

A.cpp:

A::~A()
{}

从技术上讲,它不再是纯抽象的基类,但在功能上是相同的。您可以通过基指针进行安全销毁,它仍然允许复制和移动构造继承的类,并且可以避免二进制文件中出现重复的 vtable。

选项 2:禁用警告

如果您愿意,可以使用Clang's diagnostic pragmas 禁用该块的警告:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables"
class A {
public:
    virtual void doSomething() = 0;
    virtual ~A() = 0;
};
#pragma clang diagnostic pop

这是您的两难选择:要么使类成为非纯抽象类,要么关闭警告。根据您的要求,您可能更喜欢其中一种,但与所有警告一​​样,您应该仔细考虑它。

【讨论】:

  • 纯抽象类什么时候没有方法了?
  • @Andrzej Abstract class= 至少一种纯虚方法。纯抽象 class== 接口 => 所有方法都是纯虚的?
  • @Ted 我还是很困惑。在我的两个示例中,我确实定义了 A::~A()(实际上是 A::A())。这如何等同于拥有 no 方法?以及为什么删除构造函数也解决了问题:class A { public: virtual ~A() = 0; };(假设A::A()在test.cc中也被删除了)?
  • 您的示例没有离线虚拟方法定义。您有一个外联非虚拟方法 (A()) 和内联虚拟方法 (~A())。像构造函数这样的非虚拟方法是无关紧要的,因为它们不需要 vtable。像~A() 这样的虚方法需要一个vtable,但没有一个实现。你需要的是一个out-of-line virtual method。我最初回答了一个纯抽象类,因为这是问题的标题,但给出的示例确实不是纯抽象的,因为它有一个用户定义的构造函数。
  • 另一种解决方案是添加带有实现的私有虚拟 nullop 方法吗?这将使您不必重写构造函数/赋值运算符。虽然如果我没记错的话,它也会增加 vtable 的大小(从而增加每个类实例的大小)......
猜你喜欢
  • 2014-07-07
  • 2011-01-06
  • 2015-08-14
  • 1970-01-01
  • 2015-05-01
  • 2012-10-01
  • 2017-04-28
相关资源
最近更新 更多