1

I'm working on clearing some warnings that show up on both GCC and MSVC when elevated warnings are in use. I'm catching a "class has virtual functions, but destructor is not virtual instances of this class may not be destructed correctly" under MSVC. I get a similar warning under GCC and Clang when the appropriate warnings are enabled. I'm trying to understand what the complaint is.

The class in question has no destructor (virtual or otherwise; authored by humans). The same is true of its base classes. The class has stack allocated member objects, but nothing allocated with new. The class is a template. And finally, sometimes, the classes in the hierarchy are marked as "no vtable" (but this is not this case).

If I add an empty virtual destructor, then the warning is cleared. But I want to make sure I'm not missing something non-obvious given some of the constraints above.

Here are the questions I have related to the compiler and the warnings:

  • Why is the compiler generated destructor not virtual?
  • Why is the compiler generated destructor not sufficient?
  • What is the difference between an empty dtor provided by me and a default dtor provided by the compiler?
  • Does templates affect what the compiler can generate?
  • Does "no vtable" affect what the compiler can generate?

If the answers above are effectively "I need to learn to work with my tools", then I'm happy to provide empty dtors to work with the analysis tools. (I think its very disingenuous to claim the tools aren't as smart as the programmer, so disable them. The bad guys probably laugh all the way to the bank picking the low hanging fruit the developer could not be bothered with...).


Here's one of the classes that gets tagged with the warning.

template <class T>
class DL_FixedBasePrecomputationImpl : public DL_FixedBasePrecomputation<T>
{
public:
    typedef T Element;

    DL_FixedBasePrecomputationImpl() : m_windowSize(0) {}       

    // DL_FixedBasePrecomputation
    bool IsInitialized() const
        {return !m_bases.empty();}
    void SetBase(const DL_GroupPrecomputation<Element> &group, const Element &base);
    const Element & GetBase(const DL_GroupPrecomputation<Element> &group) const
        {return group.NeedConversions() ? m_base : m_bases[0];}
    void Precompute(const DL_GroupPrecomputation<Element> &group, unsigned int maxExpBits, unsigned int storage);
    void Load(const DL_GroupPrecomputation<Element> &group, BufferedTransformation &storedPrecomputation);
    void Save(const DL_GroupPrecomputation<Element> &group, BufferedTransformation &storedPrecomputation) const;
    Element Exponentiate(const DL_GroupPrecomputation<Element> &group, const Integer &exponent) const;
    Element CascadeExponentiate(const DL_GroupPrecomputation<Element> &group, const Integer &exponent, const DL_FixedBasePrecomputation<Element> &pc2, const Integer &exponent2) const;

private:
    void PrepareCascade(const DL_GroupPrecomputation<Element> &group, std::vector<BaseAndExponent<Element> > &eb, const Integer &exponent) const;

    Element m_base;
    unsigned int m_windowSize;
    Integer m_exponentBase;         // what base to represent the exponent in
    std::vector<Element> m_bases;   // precalculated bases
};

Here are some related questions:

Community
  • 1
  • 1
jww
  • 97,681
  • 90
  • 411
  • 885
  • By the way, that's a pretty awful interface. Your class should be initialized in the constructor, always. If you ever have a need for something like Initialize() or IsInitialized(), you've done it wrong. – Puppy Jun 29 '15 at 09:17
  • @Puppy - I think they are effectively private classes used internally, and they provide lazy initialization. Its also possible to construct one without the group parameters (say, when generating a new key over a user selected field). I *think* the design came from something MFC-like, where the lower C++ object was constructed, and the the Windows specific stuff was initialized. But, it is what it is... – jww Jun 29 '15 at 09:26

2 Answers2

3

The class in question has no destructor (virtual or otherwise).

Fun fact: All objects in C++ have a destructor. It's simply that if you don't define one, the compiler generates it, and for primitives, it's a no-op.

Why is the compiler generated destructor not virtual?

Because people complained that it would adversely affect their performances if every single class in every C++ program had a virtual destructor.

Why is the compiler generated destructor not sufficient?

It's perfectly sufficient- for destroying the class it's generated for. Destroying it's derived classes, if any, is a purpose it's not supposed to serve.

What is the difference between an empty dtor provided by me and a default dtor provided by the compiler?

If you didn't define it as virtual, then the empty dtor will prohibit optimizations (sometimes) and that's it. Otherwise, the difference is obvious- yours is virtual (in addition). Some templates may require that your class is trivially destructible but it's not common.

Does templates affect what the compiler can generate?

No.

Does "no vtable" affect what the compiler can generate?

No.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    I think you can have a virtual, non-user-provided destructor, with `virtual ~X() = default;`, right? And of course all derived but not user-declared destructors are such. – Kerrek SB Jun 29 '15 at 09:29
  • I think you can have a virtual defaulted destructor, but it doesn't really make any difference to default it since it's therefore not trivial. – Puppy Jun 29 '15 at 09:34
  • Yes, it's non-trivial, but at least it can be not user-provided... should anyone care :-) – Kerrek SB Jun 29 '15 at 09:44
  • *"Fun fact: All objects in C++ have a destructor...."* - yes, of course. I meant we did not provide one. Sorry about the confusion. – jww Jun 29 '15 at 09:47
  • So I'm clear... If nothing derives from `DL_FixedBasePrecomputationImpl`, then there's no need for `virtual` in the dtor because a non-virtual dtor will cleanup properly. Is that correct? – jww Jun 29 '15 at 10:00
  • 1
    @jww: Yep, although it's a pretty dangerous assumption to make that nobody will ever inherit from it. Furthermore, if it inherits from a base with a vvirtual dtor then the dtor will be virtual anyway. – Puppy Jun 29 '15 at 10:12
  • *"Destroying it's derived classes, if any, is a purpose it's not supposed to serve"* - I'm not clear on this. When is a destructor responsible for cleaning up something other than its own class members? – jww Jun 29 '15 at 10:14
1

The compiler generated destructor is not virtual unless the class has a base with a virtual destructor.

The reason is the "zero overhead if not used" rule - virtual functions introduced a cost and, unless there is some reason for a destructor to be virtual (e.g. a base has a virtual destructor, or the programmer declared one) the destructor will not be virtual.

There is no difference between an empty destructor supplied by the compiler and one provided by the programmer, unless the compiler-generated compiler is non-virtual and the programmer declares the destructor as virtual.

Since templates are a compile-time mechanism, they affect what the compiler DOES generate and CAN generate. That is too general a question to answer more specifically.

Your last question is compiler specific. Generally speaking, with a compiler that uses vtables, a class without a vtable cannot have virtual functions and - if a class declares any function as virtual or it inherits from a class with any virtual function - it will get a vtable. In some cases (multiple inheritence) the class may have multiple vtables.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • *"The compiler generated destructor is not virtual unless the class has a base with a virtual destructor..."* - I thought that was one of the rules in play. You should comment on this answer: [Does calling a destructor explicitly destroy an object completely?](https://stackoverflow.com/questions/1036019/does-calling-a-destructor-explicitly-destroy-an-object-completely/1036099#comment50237324_1036099) – jww Jun 29 '15 at 09:51