clang:没有外部虚拟方法定义(纯抽象 C++ 类)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28786473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
clang: no out-of-line virtual method definitions (pure abstract C++ class)
提问by banach-space
I'm trying to compile the following simple C++ code using Clang-3.5:
我正在尝试使用 Clang-3.5 编译以下简单的 C++ 代码:
test.h:
测试.h:
class A
{
public:
A();
virtual ~A() = 0;
};
test.cc:
测试.cc:
#include "test.h"
A::A() {;}
A::~A() {;}
The command that I use for compiling this (Linux, uname -r: 3.16.0-4-amd64):
我用于编译它的命令(Linux,uname -r: 3.16.0-4-amd64):
$clang-3.5 -Weverything -std=c++11 -c test.cc
And the error that I get:
我得到的错误:
./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]
Any hints why this is emitting a warning? The virtual destructor is not inlined at all. Quite the opposite, there's a out-of-line definition provided in test.cc. What am I missing here?
任何提示为什么会发出警告?虚拟析构函数根本没有内联。恰恰相反,test.cc 中提供了一个外部定义。我在这里缺少什么?
Edit
编辑
I don't think that this question is a duplicate of :
What is the meaning of clang's -Wweak-vtables?as Filip Roséen suggested. In my question I specifically refer to pure abstract classes (not mentioned in the suggested duplicate). I know how -Wweak-vtables
works with non-abstract classes and I'm fine with it. In my example I define the destructor (which is pure abstract) in the implementation file. This should prevent Clang from emitting any errors, even with -Wweak-vtables
.
我不认为这个问题与以下内容重复:
clang 的 -Wweak-vtables 的含义是什么?正如 Filip Roséen 所建议的那样。在我的问题中,我特别提到了纯抽象类(在建议的副本中没有提到)。我知道-Wweak-vtables
非抽象类是如何工作的,我很好。在我的示例中,我在实现文件中定义了析构函数(它是纯抽象的)。这应该可以防止 Clang 发出任何错误,即使是-Wweak-vtables
.
采纳答案by overseas
We don't want to place the vtable in each translation unit. So there must be some ordering of translation units, such that we can say then, that we place the vtable in the "first" translation unit. If this ordering is undefined, we emit the warning.
我们不想将 vtable 放在每个翻译单元中。所以必须有一些翻译单元的排序,这样我们就可以说,我们将 vtable 放在“第一个”翻译单元中。如果此顺序未定义,我们会发出警告。
You find the answer in the Itanium CXX ABI. In the section about virtual tables (5.2.3) you find:
您可以在Itanium CXX ABI 中找到答案。在有关虚拟表 (5.2.3) 的部分中,您会发现:
The virtual table for a class is emitted in the same object containing the definition of its key function, i.e. the first non-pure virtual function that is not inline at the point of class definition. If there is no key function, it is emitted everywhere used. The emitted virtual table includes the full virtual table group for the class, any new construction virtual tables required for subobjects, and the VTT for the class. They are emitted in a COMDAT group, with the virtual table mangled name as the identifying symbol. Note that if the key function is not declared inline in the class definition, but its definition later is always declared inline, it will be emitted in every object containing the definition.
NOTE: In the abstract, a pure virtual destructor could be used as the key function, as it must be defined even though it is pure. However, the ABI committee did not realize this fact until after the specification of key function was complete; therefore a pure virtual destructor cannot be the key function.
类的虚拟表在包含其关键函数定义的同一对象中发出,即在类定义点未内联的第一个非纯虚函数。如果没有 key 函数,它会在所有使用的地方发出。发出的虚拟表包括类的完整虚拟表组、子对象所需的任何新构造虚拟表以及类的 VTT。它们在 COMDAT 组中发出,以虚拟表损坏的名称作为标识符号。请注意,如果 key 函数未在类定义中声明为内联,但其定义后始终声明为内联,则它将在包含该定义的每个对象中发出。
笔记:抽象地说,可以使用纯虚拟析构函数作为关键函数,因为即使它是纯的,也必须定义它。然而,直到关键功能的规范完成后,ABI 委员会才意识到这一事实;因此纯虚析构函数不能成为关键函数。
The second section is the answer to your question. A pure virtual destructor is no key function. Therefore, it is unclear where to place the vtable and it is placed everywhere. As a consequence we get the warning.
第二部分是你问题的答案。纯虚拟析构函数不是关键函数。所以不清楚vtable放在哪里,到处放。结果,我们得到了警告。
You will even find this explanation in the Clang source documentation.
您甚至可以在Clang 源文档中找到此解释。
So specifically to the warning: You will get the warning when all of your virtual functions belong to one of the following categories:
特别是警告:当您的所有虚拟函数都属于以下类别之一时,您将收到警告:
inline
is specified forA::x()
in the class definition.struct A { inline virtual void x(); virtual ~A() { } }; void A::x() { }
B::x() is inline in the class definition.
struct B { virtual void x() { } virtual ~B() { } };
C::x() is pure virtual
struct C { virtual void x() = 0; virtual ~C() { } };
(Belongs to 3.) You have a pure virtual destructor
struct D { virtual ~D() = 0; }; D::~D() { }
In this case, the ordering could be defined, because the destructor must be defined, nevertheless, by definition, there is still no "first" translation unit.
inline
是A::x()
在类定义中指定的。struct A { inline virtual void x(); virtual ~A() { } }; void A::x() { }
B::x() 在类定义中内联。
struct B { virtual void x() { } virtual ~B() { } };
C::x() 是纯虚拟的
struct C { virtual void x() = 0; virtual ~C() { } };
(属于 3。)你有一个纯虚析构函数
struct D { virtual ~D() = 0; }; D::~D() { }
在这种情况下,可以定义排序,因为必须定义析构函数,但是,根据定义,仍然没有“第一个”翻译单元。
For all other cases, the key function is the first virtualfunction that does not fit to one of these categories, and the vtable will be placed in the translation unit where the key function is defined.
对于所有其他情况,键函数是第一个不适合这些类别之一的虚函数,vtable 将放置在定义键函数的翻译单元中。
回答by Leon
For a moment, let's forget about pure virtual functions and try to understand how the compiler can avoid emitting the vtable in all translation units that include the declaration of a polymorphic class.
让我们暂时忘记纯虚函数,并尝试了解编译器如何避免在所有包含多态类声明的翻译单元中发出 vtable。
When the compiler sees the declaration of a class with virtual functions, it checks whether there are virtual functions that are only declared but not defined inside the class declaration. If there is exactly one such function, the compiler knows for sure that it mustbe defined somewhere (otherwise the program will not link), and emits the vtable only in the translation unit hosting the definition of that function. If there are multiple such functions, the compiler choses one of them using some deterministic selection criteria and - with regard to the decision of where to emit the vtable - ignores the other ones. The simplest way to select such a single representative virtual function is to take the first one from the candidate set, and this is what clang does.
当编译器看到一个带有虚函数的类的声明时,它会检查在类声明中是否存在只声明但未定义的虚函数。如果只有一个这样的函数,编译器肯定知道它必须在某处定义(否则程序将无法链接),并且仅在承载该函数定义的翻译单元中发出 vtable。如果有多个这样的函数,编译器会使用一些确定性的选择标准来选择其中一个,并且 - 关于在哪里发出 vtable 的决定 - 忽略其他的。选择这样一个具有代表性的虚函数最简单的方法是从候选集中取第一个,这就是 clang 所做的。
So, the key to this optimization is to select a virtual method such that the compiler can guarantee that it will encounter a (single) definition of that method in some translation unit.
因此,这种优化的关键是选择一个虚方法,以便编译器可以保证它会在某个翻译单元中遇到该方法的(单个)定义。
Now, what if the class declaration contains pure virtual functions? A programmer canprovide an implementation for a pure virtual function but (s)he is not obliged to! Therefore pure virtual functions do not belong to the list of candidate virtual methods from which the compiler can select the representative one.
现在,如果类声明包含纯虚函数怎么办?程序员可以为纯虚函数提供实现,但他没有义务!因此,纯虚函数不属于编译器可以从中选择代表性的候选虚方法列表。
But there is one exception - a pure virtual destructor!
但有一个例外——纯虚拟析构函数!
A pure virtual destructor is a special case:
纯虚拟析构函数是一个特例:
- An abstract class doesn't make sense if you are not going to derive other classes from it.
- A subclass' destructor always calls the base class' destructor.
- The destructor of a class deriving from a class with a virtual destructor is automatically a virtual function.
- All virtual functions of all classes, that the program creates objects of, are usuallylinked into the final executable (including the virtual functions that can be statically proved to remain unused, though that would require static analysis of the full program).
- Therefore a pure virtual destructor musthave a user-provided definition.
- 如果您不打算从中派生其他类,则抽象类没有意义。
- 子类的析构函数总是调用基类的析构函数。
- 从具有虚析构函数的类派生的类的析构函数自动是虚函数。
- 程序为其创建对象的所有类的所有虚函数通常都链接到最终的可执行文件中(包括可以静态证明未使用的虚函数,尽管这需要对整个程序进行静态分析)。
- 因此,纯虚拟析构函数必须具有用户提供的定义。
Thus, clang's warning in the question's example is not conceptually justified.
因此,clang 在问题示例中的警告在概念上是不合理的。
However, from the practical point of view the importance of that example is minimal, since a pure virtual destructor is rarely, if at all, needed. I can't imagine a more or less realistic case where a pure virtual destructor won't be accompanied by another pure virtual function. But in such a setup the need for the pureness of the (virtual) destructor completely disappears, since the class becomes abstract due to the presence of other pure virtual methods.
然而,从实践的角度来看,该示例的重要性微乎其微,因为几乎不需要纯虚拟析构函数。我无法想象一个或多或少的现实情况,一个纯虚析构函数不会伴随另一个纯虚函数。但是在这样的设置中,对(虚拟)析构函数的纯粹性的需求完全消失了,因为由于其他纯虚拟方法的存在,类变得抽象。
回答by Ted Percival
I ended up implementing a trivial virtual destructor, rather than leaving it pure virtual.
我最终实现了一个简单的虚拟析构函数,而不是让它纯虚拟。
So instead of
所以代替
class A {
public:
virtual ~A() = 0;
};
I use
我用
class A {
public:
virtual ~A();
};
Then implement the trivial destructor in a .cpp file:
然后在 .cpp 文件中实现简单的析构函数:
A::~A()
{}
This effectively pins the vtable to the .cpp file, instead of outputting it in multiple translation units (objects), and successfully avoids the -Wweak-vtables warning.
这有效地将 vtable 固定到 .cpp 文件,而不是在多个翻译单元(对象)中输出它,并成功避免了 -Wweak-vtables 警告。
As a side effect of explicitly declaring the destructor you no longer get the default copy and move operations. See https://stackoverflow.com/a/29288300/954for an example where they are redeclared.
作为显式声明析构函数的副作用,您不再获得默认的复制和移动操作。有关重新声明它们的示例,请参阅https://stackoverflow.com/a/29288300/954。
回答by R.N.V.
This can be solved in three ways.
这可以通过三种方式解决。
Use at least one virtual function which is not inline. Defining a virtual destructor is also alright as far as it is not an inline function.
Disable the warning as shown below.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables"
class ClassName : public Parent
{
...
};
#pragma clang diagnostic pop
- Use only .h files for class declarations.
至少使用一个非内联的虚函数。定义一个虚拟析构函数也可以,只要它不是内联函数。
禁用警告,如下所示。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables"
class ClassName : public Parent
{
...
};
#pragma clang diagnostic pop
- 仅使用 .h 文件进行类声明。