Understanding warning C4265: class has virtual functions, but destructor is not virtual, part 1

Raymond Chen

Raymond

A few customers have noted that compiling WRL and C++/WinRT headers generate warning C4265: class has virtual functions, but destructor is not virtual.

What’s this warning trying to say?

If a non-final class has a virtual method, then the compiler considers the possibility that there will be a derived class which overrides that virtual method. And if the class does not have a virtual destructor when a pointer to the base class is deleted, then only the destructor for the base class will run; the destructor for the derived class will not run.¹

struct Base
{
  virtual void f();
};

struct Derived : Base
{
 ~Derived();
};

In the above case, the warning is generated because of this possibility:

void Finish(Base *base)
{
 delete base;
}

void Something()
{
  auto d = new Derived();
  Finish(d);
}

If a pointer points to a base class, but the pointer points at runtime to a derived class, then the base class must have a virtual destructor in order to destruct the derived class. Without the virtual destructor, only the destructor for the base class will run.

However, if you never do a delete base, then the problem never arises.

The compiler is warning about a potential trap, rather then waiting for you to fall into the trap. This means that if you are careful to avoid the trap, you have no actual problem, but the compiler will warn you about it anyway.²

If you know that nobody derives from Derived, you can mark it as final, which will make the warning go away.

Next time, we’ll apply this understanding to WRL and C++/WinRT.

¹ Formally, the behavior is undefined. See [expr.delete] paragraph 3.

If the static type of the object to be deleted is different from its dynamic type…, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

² I would prefer the compiler wait until you actually do the delete before generating the warning. But it’s being proactive and warning about a potential problem, even if that potential is not realized immediately.

5 comments

Comments are closed.

  • Avatar
    Jeremy Richards

    “I would prefer the compiler wait until you actually do the delete before generating the warning. But it’s being proactive and warning about a potential problem, even if that potential is not realized immediately.”

    How would that even work? I assume you would at least need something like link time code generation enabled since you could easily be deleting the base class pointer in one file that has no idea that that class was inherited somewhere completely different.

    • Raymond Chen
      Raymond ChenMicrosoft logo

      It would be when the compiler observes a “delete p” where p is (1) of compile-time type Base*, (2) Base is unsealed, (3) Base has virtual methods, and (4) Base does not have a virtual destructor. “Deleting unsealed class with virtual methods but no virtual destructor.” Right now, it complains at the declaration of Base rather than at its deletion.

      • Avatar
        Michael

        The C5205 warning introduced in 2019 Update 5 almost does what you want, but it requires one of those virtual functions to be pure :-/ Of course, you may be going to mentioning this warning, but I can’t quite tell.

        Another consequence of the UB for this is sized deallocation: If Derived adds any data members, operator delete will receive the wrong size. So even if all of the involved destructors are trivial, it’s not a “harmless” UB. e.g. https://gcc.godbolt.org/z/EpQw2s

  • Avatar
    Neil Henderson

    Is there a typo? C4254 is ‘operator’ : conversion from ‘type1’ to ‘type2’, possible loss of data
    I think you meant C4265: ‘class’ : class has virtual functions, but destructor is not virtual

    Also, it wasn’t clear to me why ‘final’ would hide this warning. AFAIK, ‘final’ on a derived class doesn’t introduce a virtual destructor in the base, so therefore the case the compiler is trying to warn about may still be there.

    I realise you’re talking about a situation where “If you know that nobody derives from Derived, you can mark it as final, which will make the warning go away.” But I’m wondering why does the compiler hide the warning just because Derived is marked final?

    // Final hides the C4265 warning but deleting
    // Derived via a pointer to Base is still undefined.
    struct Derived final : Base
    {
        Derived()
        {
            p = new char[50];
        }
    
        ~Derived()
        {
            delete[] p;  // Does not run
        }
    
        char *p;
    };
    • Avatar
      Scarlet Manuka

      I think the text should be “If you know that noboody derives from Base”. The article is saying the error occurs on Base because of the possibility that someone will create a Derived class and delete it as a Base, implying that Derived doesn’t necessarily exist in the code at present. If we mark Base as final then we know nobody will create Dervived and so the warning goes away.