clang: no out-of-line virtual method definitions (pure abstract C++ class)

31,937

Solution 1

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.

You find the answer in the Itanium CXX ABI. In the section about virtual tables (5.2.3) you find:

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.

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.

You will even find this explanation in the Clang source documentation.

So specifically to the warning: You will get the warning when all of your virtual functions belong to one of the following categories:

  1. inline is specified for A::x() in the class definition.

    struct A {
        inline virtual void x();
        virtual ~A() {
        }
    };
    void A::x() {
    }
    
  2. B::x() is inline in the class definition.

    struct B {
        virtual void x() {
        }
        virtual ~B() {
        }
    };
    
  3. C::x() is pure virtual

    struct C {
        virtual void x() = 0;
        virtual ~C() {
        }
    };
    
  4. (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.

For all other cases, the key function is the first virtual function 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.

Solution 2

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.

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 must be 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.

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 can provide 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:

  1. An abstract class doesn't make sense if you are not going to derive other classes from it.
  2. A subclass' destructor always calls the base class' destructor.
  3. The destructor of a class deriving from a class with a virtual destructor is automatically a virtual function.
  4. All virtual functions of all classes, that the program creates objects of, are usually linked 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).
  5. Therefore a pure virtual destructor must have a user-provided definition.

Thus, clang's warning in the question's example is not conceptually justified.

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.

Solution 3

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:

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.

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/954 for an example where they are redeclared.

Solution 4

This can be solved in three ways.

  1. 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.

  2. Disable the warning as shown below.

    #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" class ClassName : public Parent { ... }; #pragma clang diagnostic pop

  3. Use only .h files for class declarations.
Share:
31,937

Related videos on Youtube

banach-space
Author by

banach-space

From [1]: A Banach space is a normed space which is complete in the metric defined by its norm; this means that every Cauchy sequence is required to converge. Also, later in the same reference: Many of the best-known function spaces are Banach spaces. [1] Functional Analysis, Walter Rudin, McGraw-Hill, 1991

Updated on July 09, 2022

Comments

  • banach-space
    banach-space almost 2 years

    I'm trying to compile the following simple C++ code using Clang-3.5:

    test.h:

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

    test.cc:

    #include "test.h"
    
    A::A() {;}
    A::~A() {;}
    

    The command that I use for compiling this (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?

    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.

    • banach-space
      banach-space about 9 years
      @gha.st Indeed, but what difference doesn't make? I came across this problem while implementing a more complex abstract class, but that's irrelevant here. The code above should still be fine, right?
    • Barry
      Barry over 7 years
      Isn't this an exact dupe of this other question that you asked 3 hours later?
    • Oktalist
      Oktalist over 7 years
      @Barry I think this is the better question of the two, so this is the one I bountied. The other question explains that at the time of asking that one, this one was closed as a dupe of another, but was later reopened.
    • R.N.V.
      R.N.V. over 4 years
      This can be solved in two ways.
    • R.N.V.
      R.N.V. over 4 years
      This can be solved in three ways. 1. 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. 2. Disable the warning as shown below. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" class ClassName : public Parent { ... }; #pragma clang diagnostic pop
  • banach-space
    banach-space about 9 years
    Your suggestion could be simplified as: don't use pure abstract classes and you won't get the warning. How does this relate to my question? It's clear that the author's intention is to indeed use a pure abstract class.
  • T Percival
    T Percival about 9 years
    It's still conceptually a pure abstract base class, but without the side effect of duplicating the vtable.
  • T Percival
    T Percival about 9 years
    That's what I said in the first sentence. I am providing a possible solution. I provided a longer description of two possible solutions in your repost of this question: stackoverflow.com/questions/28788353/… - where I included an example of disabling the warning if you want to keep it pure abstract.
  • banach-space
    banach-space about 9 years
    The question was: "Any hints why this is emitting a warning?". I didn't ask for a solution and you're not providing one - it's a workaround which was earlier mentioned here stackoverflow.com/questions/28788353/… .
  • T Percival
    T Percival about 9 years
    This is a Q and A site. You posted a Q, I posted an A. Other people might be looking for a solution rather than just an explanation.
  • Mark Ransom
    Mark Ransom over 7 years
    Also see Herb Sutter's thoughts on pure virtual destructors. The only situation where I can imagine a pure virtual destructor being useful as the only virtual method is when you want a collection of pointers, using dynamic_cast to work with the pointers.