11
#include <iostream>
using namespace std;
class base
{
   int a;
 public: 
   base() {a =0;}
 };
 class derv :public base
 {
   int b;
  public:
   derv() {b =1;}
 };
 int main()
 {
    base *pb = new derv();
    delete pb;
 }

I don't have a virtual destructor in derv class, does it delete only base part of derv object??

j0k
  • 22,600
  • 28
  • 79
  • 90
Alok
  • 1,997
  • 2
  • 18
  • 30

4 Answers4

21

It might.

Because base does not have a virtual destructor, your code exhibits undefined behavior. Anything might happen. It might appear to work as you expect. It might leak memory. It might cause your program to crash. It might format your hard drive.

A citation was requested. C++11 §5.3.5/3 states that, for a scalar delete expression (i.e., not a delete[] expression):

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.

The static type (base) is different from the dynamic type (derv) and the static type does not have a virtual destructor, so the behavior is undefined.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 2
    Lack of virtual destructor in base means that any custom finalization specified in derived classes' destructor won't execute. The memory for object would still be deallocated correctly. (The destructor of `base` would also get called if it was defined by non-virtual). – Xion Jan 06 '12 at 01:03
  • 6
    @Xion: Really. The behavior is undefined. All bets are off. Nothing can be said with certainty about the behavior of the program. – James McNellis Jan 06 '12 at 01:04
  • 2
    @wilhelmtell: It is Standard. It's undefined behaviour. – Puppy Jan 06 '12 at 01:08
  • @wilhelmtell: If you insist. :-) – James McNellis Jan 06 '12 at 01:10
  • `base::~base()` will be called, `derv::~derv()` won't. Memory will be freed after that. – lapk Jan 06 '12 at 01:11
  • 1
    Intriguing. Yet another case where in practice UB gets swept under the rug by lenient compilators. Good to know! – Xion Jan 06 '12 at 01:12
  • @JamesMcNellis thanks, that clears all doubt and should reverse all the downvotes! – wilhelmtell Jan 06 '12 at 01:13
  • @James, You said even base class doesn't have any virtual function but it must have a virtual destructor if we want to inherit the base class?? – Alok Jan 06 '12 at 01:16
  • @Xion: Actually, it is not that much swept under the rug. Eg. if the base subobject isn't at the same address as the derived object, it will call deallocation function with the wrong address. Moreover, any virtual bases' destructors might be called wrongly (with wrong `this`), although I'm not that sure about this one. – jpalecek Jan 06 '12 at 01:16
  • @Alok: If you `delete` a dynamically allocated `derv` object using a `base*` that points to it, then `base` must have a virtual destructor. – James McNellis Jan 06 '12 at 01:17
  • @Alok: No, but you must `delete` the classes' objects only through the pointers to most derived subobjects then. – jpalecek Jan 06 '12 at 01:17
  • @jpalecek Is there a single compiler that actually places `base` members after `derv` members? The memory layout is, indeed, unspecified by the Standard, but in practice `base` is always placed first. We are talking about non-virtual inheritance here. – lapk Jan 06 '12 at 01:23
  • @AzzA: I cannot answer your question, as I am not familiar with every single compiler. (I'm sure I couldn't even name them all!) – James McNellis Jan 06 '12 at 01:25
  • @wilhelmtell: I agree. It is occasionally useful to provide citations. However, they can also make an answer confusing or overly technical and pedantic, so I tend to avoid doing so unless one is requested (which is fine) or the subject requires it, e.g. if some nuance needs to be pointed out or if there is disagreement as to what the correct behavior is. – James McNellis Jan 06 '12 at 01:27
  • @JamesMcNellis I agree with your answer in principle - the simple code posted will, most likely, never go wrong, but it's extremely unwise to rely on compiler implementations. Even if they seem to be same for all compilers. I was just simply curious, if someone actually knew of compiler that does "unusual" memory placement. – lapk Jan 06 '12 at 01:32
  • @Xion: What do you mean swept under the rug? Note that the standard explicitly states a compiler does *not* have to report when it compiles undefined behavior. – GManNickG Jan 06 '12 at 01:38
  • This issue comes up again and again on StackOverflow. Is there a way to fix this in C++2x? Should C++ split pointer types into two types? Instead of `T*`, there should be `deletable_ptr` and `notdeletable_ptr`. (I'm aware that the modern approach might use something like `shared_ptr`, but I'm looking for a 'thin' approach that is closest to the underlying pointer, but which still ensures correctness). `new` shall return a `deletable_ptr`, and any attempt to convert to a base class shall convert to an `undeletable_ptr`, unless the base has a virtual constructor of course. – Aaron McDaid Jan 06 '12 at 01:40
  • @AaronMcDaid: There's nothing to stop you from making your own utilities, of course, but at some point either the language will change so much or your utility use will be so wide-spread that you're no longer programming in C++. What you're asking for is a "real" static and strong type-system, which is pretty much a lost cause in C++. – GManNickG Jan 06 '12 at 04:36
1

In your source code there is no memory leak, since you don't have any member variable that is created dynamically.

Consider the modified example below Case 1:

#include <iostream>
using namespace std;
class base
{
   int a;
 public: 
   base() {a =0;}
   ~base() 
     {
       cout<<"\nBase Destructor called";

     }
 };
 class derv :public base
 {
   int *b;

  public:
   derv() { b = new int;}
  ~derv()
  {
      cout<<"\nDerv Destructor called"; 
      delete b;
  }
 };
 int main()
 {
    base *pb = new derv();
    delete pb;
 }

In this case the output will be,

   Base Destructor called

In this case there is a memory leak, because 'b' is created dynamically using 'new' which should be deleted using 'delete' keyword. Since derv destructor is not being called it's not deleted so there is memory leak.

Consider the below case 2:

#include <iostream>
using namespace std;
class base
{
   int a;
 public: 
   base() {a =0;}
   virtual ~base() 
     {
       cout<<"\nBase Destructor called";

     }
 };
 class derv :public base
 {
   int *b;

  public:
   derv() { b = new int;}
  ~derv()
  {
      cout<<"\nDerv Destructor called"; 
      delete b;
  }
 };
 int main()
 {
    base *pb = new derv();
    delete pb;
 }

In the case 2 output will be,

Derv Destructor called 
Base Destructor called

In this case there is no memory leak.because derv destructor is called and b is getting deleted.

Destructor can be defined as Virtual in base class to make sure the derived class destructor to be called when we delete base class pointer which is pointing to derived class object.

We can say 'Destructor must be virtual when derived class has dynamically created members'.

srajeshnkl
  • 883
  • 3
  • 16
  • 49
  • 1
    The behavior is undefined. All bets are off. Nothing can be said with certainty about the behavior of the program. – James McNellis Jan 06 '12 at 18:03
  • 1
    @JamesMcNellis: In the above code which has memory allocation in derived class should have virtual destructor. Otherwise there is a memory leak. – srajeshnkl Jan 09 '12 at 03:47
0

To add to the discussion. GCC will throw an error if you ask it to do memory sanitizing on code where the destructor is not defined as virtual. You need to use

-fsanitize=address

as a compile option.

For example

#include <cstdio>

class Base
{
public:
 Base()
 {
    printf("ctor Base\n");
 }

  ~Base()
 {
    printf("dtor Base\n");
 }
};

class Derived : public Base {
private:
  double val;

public:
 Derived(const double& _val)
 : val(_val)
 {
    printf("ctor Derived\n");
 }

 ~Derived()
 {
    printf("dtor Derived\n");
 }
};

int main() {
    Derived* d = new Derived(5);
    Base* p = d;
    // Derived destructor not called!!
    delete p;
    //delete d;
}

when compiled will output the following error

ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 1
=================================================================
==1==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x602000000010 in thread T0:
  object passed to delete has wrong type:
  size of the allocated type:   8 bytes;
  size of the deallocated type: 1 bytes.
    #0 0x7fc6ed4db428 in operator delete(void*, unsigned long) (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdc428) (BuildId: c9b24be17e4cbd04bdb4891782c4425e47a9259a)
    #1 0x401177 in main /app/example.cpp:38
    #2 0x7fc6ece58082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)
    #3 0x4011fd in _start (/app/output.s+0x4011fd) (BuildId: 2751b7982ec6bd6f9c9092db77c9bfa142055e2d)

0x602000000010 is located 0 bytes inside of 8-byte region [0x602000000010,0x602000000018)
allocated by thread T0 here:
    #0 0x7fc6ed4da528 in operator new(unsigned long) (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdb528) (BuildId: c9b24be17e4cbd04bdb4891782c4425e47a9259a)
    #1 0x40112f in main /app/example.cpp:35

SUMMARY: AddressSanitizer: new-delete-type-mismatch (/opt/compiler-explorer/gcc-13.1.0/lib64/libasan.so.8+0xdc428) (BuildId: c9b24be17e4cbd04bdb4891782c4425e47a9259a) in operator delete(void*, unsigned long)
==1==HINT: if you don't care about these errors you may set ASAN_OPTIONS=new_delete_type_mismatch=0
==1==ABO 

See https://godbolt.org/z/aaTrovaPE

If however you declare a virtual destructor on base then the error goes away.

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
-1

There is no memory leak in your code. There would have been a memory leak if you needed to free some memory in the derived class destructor.

Viral
  • 245
  • 1
  • 2
  • 8