Bug 53596 - g++-4.7 -Wall shouldn't complain for non-virtual protected dtor
Summary: g++-4.7 -Wall shouldn't complain for non-virtual protected dtor
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.7.0
: P3 minor
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-06-06 19:53 UTC by kim.walisch
Modified: 2012-06-07 08:53 UTC (History)
0 users

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments
g++-4.7 -Wall produces erronous [-Wdelete-non-virtual-dtor] warning (201 bytes, application/octet-stream)
2012-06-06 19:53 UTC, kim.walisch
Details

Note You need to log in before you can comment on or make changes to this bug.
Description kim.walisch 2012-06-06 19:53:59 UTC
Created attachment 27569 [details]
g++-4.7 -Wall produces erronous [-Wdelete-non-virtual-dtor] warning

g++-4.7 shouldn't complain for non-virtual protected dtor if the base class has virtual functions. Previous versions of g++ do not warn, other compilers (msvc /W4, sunCC +w2) do not warn.

terminal log:

[walki@walki Desktop]$ g++-4.7 -Wall test.cpp 
test.cpp: In function ‘int main()’:
test.cpp:19:10: warning: deleting object of polymorphic class type ‘Derived’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]

/////////////////////////////////////////////////////////////////////////
// file: test.cpp

#include <iostream>

class Base {
public:
  virtual void foo() = 0;
protected:
  ~Base() { } // legal C++ code
};

class Derived : public Base {
public:
  virtual void foo() { std::cout << "Derived::foo()"      << std::endl; }
  ~Derived()         { std::cout << "Derived::~Derived()" << std::endl; }
};

int main() {
  Derived* derived = new Derived();
  derived->foo();
  delete derived; // legal, there must be no warning!
  return 0;
}

/////////////////////////////////////////////////////////////////////////
Comment 1 Jonathan Wakely 2012-06-06 22:43:23 UTC
The warning is valid, the fact Base is protected is irrelevant, you're not using delete with a Base* operand.

Consider:

class MoreDerived : public Derived {
public:
  ~Derived()         { std::cout << "MoreDerived::~MoreDerived()" << std::endl; }
};

int main() {
  Derived* morederived = new MoreDerived();
  morederived->foo();
  delete morederived; // undefined behaviour
}

Now the warning is entirely correct, you are deleting a Derived* which is a polymorphic type without a virtual destructor, so the program has undefined behaviour.  That's substantially the same as your example, on the line:

  delete derived;

the compiler doesn't know that the Derived* points to a Derived or a MoreDerived, so it warns you it might cause undefined behaviour.

N.B. Clang (known for much better diagnostics than MSVC or Sun CC) also warns for your example:

w.cc:19:3: warning: delete called on 'Derived' that has virtual functions but non-virtual destructor [-Wdelete-non-virtual-dtor]
  delete derived; // legal, there must be no warning!
  ^
1 warning generated.
Comment 2 Jonathan Wakely 2012-06-06 22:45:00 UTC
The reason previous versions of GCC do not warn is because GCC has been improved and now issues a warning for unsafe code that it didn't diagnose before. This is a good thing.
Comment 3 kim.walisch 2012-06-07 07:31:41 UTC
Hi, thanks for your answer but I still think it is a bug. Here are my reasons:

1) You mention that Clang also warns for my example but this seems to be a bug in Clang, see http://lists.cs.uiuc.edu/pipermail/llvmbugs/2012-March/022451.html

2) Herb Sutter wrote a Guru of the Week (GotW) article on this topic (http://www.gotw.ca/publications/mill18.htm), it says: 

Guideline #4: A base class destructor should be either public and virtual, or protected and nonvirtual.

3) If I modify 'virtual void foo()' to 'void foo()' in my previous example the warning vanishes, this makes no sense.

terminal log:

$ g++-4.7 -Wall test2.cpp 
$

/////////////////////////////////////////////////////////////////
// file: test2.cpp

#include <iostream>

class Base {
protected:
  ~Base() { }
};

class Derived : public Base {
public:
  void foo() { std::cout << "Derived::foo()"      << std::endl; }
  ~Derived() { std::cout << "Derived::~Derived()" << std::endl; }
};

int main() {
  Derived* derived = new Derived();
  derived->foo();
  delete derived; // correct, no warning!
  return 0;
}

/////////////////////////////////////////////////////////////////

4) In your MoreDerived example you can make the Derived dtor virtual, this way the protected non-virtual Base destructor is absolutely valid.

/////////////////////////////////////////////////////////////////
// file: test3.cpp

class Base {
public:
  virtual void foo() = 0;
protected:
  ~Base() { }
};

class Derived : public Base {
public:
  virtual void foo() { }
  virtual ~Derived() { }
};

class MoreDerived : public Derived {
public:
  ~MoreDerived() { }
};

int main() {
  Derived* morederived = new MoreDerived();
  morederived->foo();
  delete morederived; // no warning, virtual Derived dtor
  return 0;
}

/////////////////////////////////////////////////////////////////
Comment 4 kim.walisch 2012-06-07 08:28:05 UTC
I found another post on the web (http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110530/042333.html) that explains the warning, it says:

"If Derived (see above) gets subclassed in the future, deletion in "main()" will be undefined behavior. Ideally Derived should be marked "final" and then there will also be no warning"

Based on this explanation I think the warning is valid. Clang's warning message is somewhat more clear:

"warning: delete called on 'Derived' that has virtual functions but non-virtual destructor [-Wdelete-non-virtual-dtor]"

This also explains why there is no warning in test2.cpp of my previous post.
Comment 5 kim.walisch 2012-06-07 08:32:04 UTC
The warning is valid (see Comment 4), but the warning message is a bit unclear.
Comment 6 Jonathan Wakely 2012-06-07 08:46:09 UTC
(In reply to comment #3)
> Hi, thanks for your answer but I still think it is a bug. Here are my reasons:
> 
> 1) You mention that Clang also warns for my example but this seems to be a bug
> in Clang, see
> http://lists.cs.uiuc.edu/pipermail/llvmbugs/2012-March/022451.html

I expect that bug will get closed, Clang behaves as designed.
 
> 2) Herb Sutter wrote a Guru of the Week (GotW) article on this topic
> (http://www.gotw.ca/publications/mill18.htm), it says: 
> 
> Guideline #4: A base class destructor should be either public and virtual, or
> protected and nonvirtual.

The compiler cannot know Derived is not a base class (unless you mark it "final" to prevent derivation, or "__final" as a GCC extension in C++03).  You have virtual functions, so it's reasonable to assume you plan to derive from it.
 
> 3) If I modify 'virtual void foo()' to 'void foo()' in my previous example the
> warning vanishes, this makes no sense.

Then the class isn't polymorphic, so the compiler assumes you don't plan to derive from it.


> 4) In your MoreDerived example you can make the Derived dtor virtual, this way
> the protected non-virtual Base destructor is absolutely valid.

Obviously. That fixes the undefined behaviour, so there's no warning,  that's the change the compiler is suggesting you make!

(In reply to comment #4)
> I found another post on the web
> (http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110530/042333.html)
> that explains the warning, it says:
> 
> "If Derived (see above) gets subclassed in the future, deletion in "main()"
> will be undefined behavior. Ideally Derived should be marked "final" and then
> there will also be no warning"

That's exactly what I demonstrated in my MoreDerived example.

> Based on this explanation I think the warning is valid. Clang's warning message
> is somewhat more clear:
> 
> "warning: delete called on 'Derived' that has virtual functions but non-virtual
> destructor [-Wdelete-non-virtual-dtor]"

A polymorphic class is one with virtual functions. That's standard terminology.

I prefer the GCC message, it tells you using delete might (or will) be undefined behaviour, instead of just warning about using delete.

> This also explains why there is no warning in test2.cpp of my previous post.

Exactly.
Comment 7 Manuel López-Ibáñez 2012-06-07 08:53:43 UTC
I summarized all this in http://gcc.gnu.org/wiki/VerboseDiagnostics#delete-non-virtual-dtor but I am afraid it is not a very good summary, please feel free to improve it.