Click here to Skip to main content
Rate this: bad
good
Please Sign up or sign in to vote.
See more: C++
Hi
I have a class Derived which implements an interface Base, something like this
class Base {
public:
    virtual void foo () = 0;
};
 
class Derived : public Base {
public:
    Derived (int i = 5) {
        data = new double [i];
    }
    ~Derived () {
        foo ();
    }
    void foo () {
        if (data) {
            delete []data;
        }
    }
private:
    double *data; // an array
};
The foo here as you can see does the cleaning up routine. Now I create an instance of class Derived and store it to variable of type Base:
int n;
Base *base;
n = 1024 * 1024 * 8; // some random big number
base = new Derived (n);
delete base;
After this, the used resources still not reclaimed, although the variable base is already invalid. I notice that It doesn't even go to the destructor of ~Derived() hence the resources is still there. I don't know why, maybe someone can explain this.
 
After a little while I come up with a solution:
int n;
Base *base;
n = 1024 * 1024 * 8;
base = new Derived (n);
 
Derived *p;
p = dynamic_cast<Derived*>(base);
if (p) {
    delete p;
}
Are there any other solution than this?
Posted 21-Sep-12 10:35am
Comments
Sergey Alexandrovich Kryukov at 21-Sep-12 17:46pm
   
Pretty good simple question, clearly formulated. (Unfortunately, this is so rare these days.) I voted 4 (not 5 because you still miss something :-).
Please see my solution.
Good luck,
--SA
pasztorpisti at 21-Sep-12 18:25pm
   
Roboust well designed code shouldn't employ dynamic_cast. dynamic_cast usually means you made a bad design decision somewhere. I use it rarely and even in that case only as a temporary hack to spare time for something else that is urgent.
momond19 at 21-Sep-12 20:02pm
   
yea I completely agree with you, that is why I ask for another solution :D
pasztorpisti at 21-Sep-12 20:16pm
   
The fact that you found out that this kind of delete results in bug already presumes some know-how on your side. :-D
Sergey Alexandrovich Kryukov at 21-Sep-12 21:13pm
   
What do you mean? "I know how to make a subtle bug which leaves the code looking innocent"? :-)
--SA
pasztorpisti at 22-Sep-12 6:53am
   
:-) :-) :-) Well we can approach this from different directions... However I think that being able to locate a bug is a very nice skill. Its sad but even some self claimed "veterans" are unable to debug certain bugs.
Sergey Alexandrovich Kryukov at 21-Sep-12 21:14pm
   
Or, perhaps, it could make an interview question "why this code does not work properly"?
May be not a brilliant interview question, but way too many other interview questions are way more idiotic. :-)
--SA
pasztorpisti at 22-Sep-12 6:59am
   
Our first and simplest interview question starts with a static method - no, not a static method, a simple C function buecase its far from C++ - that is around 10-20 lines long and has at lest 10 bugs/questionable algorithmic solutions in it. By the way the function transforms a string to contain only lowercase letters and replaces every '\\' to '/'. More than 90% of the applicants fails to answer very important questions about it. I would consider this virtual destructor stuff quite an advanced and unneded question to filter out de la créme. :-)
Sergey Alexandrovich Kryukov at 23-Sep-12 11:34am
   
I actually don't like answers like this: they only requires attention (which you should not expect from nervous but knowledgeable candidate because of natural stress, but they do not reveal deep knowledge). Good question is really hard to invent -- I work at it sometimes...
--SA
pasztorpisti at 23-Sep-12 11:49am
   
You might be right but that small function allows asking a lot of small questions that are easy to separate beginners and experienced ones. For example why is this bad: for (int i=0; i<strlen(s); i++) :-) I think the answer is obvious even under stress... The accompanying questions are easy. I think a very important factor is the type of knowledge you wanna test: a coder knowing every tiny details of a language might not be a good at software design.
Sergey Alexandrovich Kryukov at 21-Sep-12 21:16pm
   
By the way, I up-voted the question by the reasons I explained above.
--SA
Sergey Alexandrovich Kryukov at 21-Sep-12 21:11pm
   
I basically agree. This actually could be done safely, but this is a certain abuse of the OOP, defeats its purpose and signal about poor OOP design.
I answered how to avoid it...
--SA
pasztorpisti at 22-Sep-12 7:02am
   
Exactly, very well explained.

1 solution

Rate this: bad
good
Please Sign up or sign in to vote.

Solution 1

Your solution would work, but it is artificial and bad for maintenance. Don't do such things.
 
Instead, add a virtual destructor Base::~Base.
 
In this case, the destructor Derived::~Derived will be called even if your compile-time type you use in delete is Base. Based on this compile-time class, the call to the destructor Base::~Base will be dispatched to Derived::~Derived through late binding first, and then, because of the mechanism of automatic call base class destructor, Base::~Base will be called.
 
Try it: I tested this code before posting the answer.
 
[EDIT]
 
And fix one minor flaw: make the function foo private.
 
—SA
  Permalink  
v4
Comments
Espen Harlinn at 21-Sep-12 19:44pm
   
Right :-D
Sergey Alexandrovich Kryukov at 21-Sep-12 19:48pm
   
Thank you, Espen.
--SA
momond19 at 21-Sep-12 20:10pm
   
Nice so simple!
It works with ~Base () {}.
Thanks SA
 
Btw if I try to declare the destructor as pure virtual function virtual ~Base() = 0; It doesn't work, it still asking for the implementation at linking.
Sergey Alexandrovich Kryukov at 21-Sep-12 20:33pm
   
You are welcome.
 
Pure virtual does not work just because implementation requires all the base class destructors to be called. You can keep it pseudo-abstract if you want, which means, with empty { } block... You can also make a non-virtual (but could be also virtual) destructor only in the base which calls some virtual clean-up method. That method could be pure virtual. And then you can override it to make the class non-abstract.
 
By the way, look at C++/11 version with explicit overrides and final:
http://en.wikipedia.org/wiki/C%2B%2B11#Explicit_overrides_and_final
 
This is an attempt to modernize C++ (just a bit) by using ideas practiced in more advanced OOP implementations for a long time already (C#, Object or Delphi Pascal, Java...). It prevents a number of similar mistakes.
 
Good luck, call again.
--SA
Espen Harlinn at 22-Sep-12 4:29am
   
"which calls some virtual clean-up method" - will probably not work as expected, have a look at:
http://www.artima.com/cppsource/nevercall.html
pasztorpisti at 22-Sep-12 7:13am
   
Thats quite a dangerous thing, we avoid it by doing just basic initialization and cleanup in our constructors/destructors so I havent seen "pure virtual function call" runtime errors for so long now. A bug that is even harder to find than pure virtual function call when your call doesnt crash but not the right virtual function is called - rather one in the base classes while the object is being constructed/destructed. The problem with this is that these bugs can be so hidden, it would be nice if they could put in some runtime check for the presense of such (with a compiler switch).
Espen Harlinn at 22-Sep-12 7:26am
   
Right :-D
It's interesting to note that Meyers arguments are, from a design perspective, somewhat valid for languages such as c# and java too, as you can easily run into difficulties - which is why C++ behaves differently.
pasztorpisti at 22-Sep-12 7:46am
   
I don't know the designers intentions behind the different behaviors but I always thought that it comes from implementation details (dynamic vptr setup vs pefrect hashing) but it doesnt matter since in practice both solutions lead to disasters so I treat this as a "bug" of these languages and I think it makes no sense to decide which one is better. In C++ sometimes you call something else than what you wanted while in the other languages you call into a derived object before its property constructed, none of these come off well... :-)
Espen Harlinn at 22-Sep-12 7:53am
   
C# != C++ even if there are similarities - and this is one of the things that differ wildly ...
pasztorpisti at 22-Sep-12 7:55am
   
Fortunately. :-)
Sergey Alexandrovich Kryukov at 23-Sep-12 11:42am
   
Come on, C# has little to do with C++. C# is a new Delphi Pascal, with influence of Java. See who is the architect...
--SA
pasztorpisti at 23-Sep-12 11:56am
   
We have just found a "similarity", something that works in neither of these languages. :-)
Sergey Alexandrovich Kryukov at 23-Sep-12 12:17pm
   
If you are talking about the call from constructor, we rather found (well, a actually well-known already) difference...
--SA
pasztorpisti at 23-Sep-12 12:19pm
   
Of course, just joking! :-) We were discussing the difference between the two buggy functioning.
Sergey Alexandrovich Kryukov at 23-Sep-12 12:24pm
   
Sure. That is the main thing about .NET and Delphi Pascal (Object Pascal) -- there is no the similar problem in them, whatsoever. This is just one of purely C++ difficulties.
--SA
pasztorpisti at 23-Sep-12 12:30pm
   
No, its not a C++ only problem! Havent checked in practice how the bug "works" in C# if it "works" at all but the same thing is an existing problem in java I had to face it once unfortunately. In java you can call a derived virtual method from a base class constructor before the execution of a dervied constructor! That can cause very hard to find bugs!
Sergey Alexandrovich Kryukov at 23-Sep-12 12:33pm
   
May be not C++ only, but I don't know how it can be a problem with C#. Care to give a code sample with the problem? If this is the case, it could be just few lines...
--SA
pasztorpisti at 23-Sep-12 12:51pm
   
Tried it, perfectly the same defect as in java. In C#/Java the "vtable" is the same during the whole construction phase so basically you can call into the derived part of the object from the base calss before the derived part is properly initialized by its constructor. What makes things a bit nicer is that the whole object is at least zero initilaized.
Example:
<pre lang="C#">
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SharpConsoleTest
{
class Base
{
protected Base()
{
Console.WriteLine("Base ctor");
f();
}
 
public virtual void f()
{
Console.WriteLine("Base.f()");
}
}
 
class Derived : Base
{
public Derived()
{
Console.WriteLine("Derived ctor");
}
 
public override void f()
{
Console.WriteLine("Derived.f()");
}
}
 
class Program
{
static void Main(string[] args)
{
Derived d = new Derived();
}
}
}
</pre>
 
The output of this is:
Base ctor
Derived.f()
Derived ctor
Sergey Alexandrovich Kryukov at 23-Sep-12 14:31pm
   
We probably did not understand each other again. I know the problem you just described and its nature. Above, I expressed the doubt that you can demonstrate some problem related to the call of virtual function from a destructor.
--SA
pasztorpisti at 23-Sep-12 14:41pm
   
I thought that a few comments before we switched to discussing ctors, but never mind! These comments may come handy for some because they describe some hard to find pitfalls! :-)
Sergey Alexandrovich Kryukov at 23-Sep-12 14:56pm
   
I agree.
--SA
Sergey Alexandrovich Kryukov at 23-Sep-12 11:41am
   
I don't say this my optional advice is really good, but perhaps you did not understand it.
 
Check it out: I never advised to call any virtual function from constructor -- with C++, this is really dangerous thing. I suggested to call a virtual function from destructor -- with is perfectly fine.
 
By the way, this danger is just yet another C++ stupidity, in my opinion. Why, after all, it is perfectly all right in .NET and Delphi Pascal? Because the conception of construction is very different. The difference is quite delicate to discuss it now, but I would strongly vote for .NET/Delphi approach.
 
--SA
pasztorpisti at 23-Sep-12 12:03pm
   
Calling a virtual function from the destructor is basically the same as in the constructor. Its thoritically dangerous and doesn't work for like the call from the constructor. As the object is constructed the vptr is replaced many times by each constructor, butt he vptr is also replaced many times as the object desctructors are being called. For this reason the call wont be dispatched to the method you wanted. Even if it were dispatched, you would call a method for the derived part of the object that has already been destructed if it has been deleted "correctly" by calling its destructor previously.
Sergey Alexandrovich Kryukov at 23-Sep-12 12:21pm
   
No! I don't know what makes you think so. And the virtual functions are called from destructor all the time and a destructor can be virtual, unlike constructor. All virtual tables are in place, no problems...
--SA
pasztorpisti at 23-Sep-12 12:26pm
   
First the destructor of the derived class and the that of the base class is called. When the derived destructor executes the vptr points ot the vtable of the derived class, and when the destructor of Base executes the vptr is already replaced to the Base class vptr. Or are we talking about the buggy delete case where the Derived destructor isn't execued?
Sergey Alexandrovich Kryukov at 23-Sep-12 12:37pm
   
We destruct the object, and the destructor calls a virtual function. At this moment, vtable is in place. The call dispatches to the implementation of the virtual function of the run-time type class. This function clean up something which may be unrelated to these class hierarchy... And where there can be a problem here?
 
And don't get me wrong, my original recipe is in my answer. I just say that such scenario can work, too, not that I recommend it...
--SA
pasztorpisti at 23-Sep-12 12:42pm
   
I think I get it - basically you delete the base class and not the derived class so only the base destructor is executed and you 'emulate' the virtual destructor with the virtual function call. But even in this case your solution might not work because the vtbl replacer code is an automatically generated codepiece at the beginning of the destructor (I guess).
Sergey Alexandrovich Kryukov at 23-Sep-12 12:46pm
   
I don't delete any class instance in the implementation in any constructors. Why do you think the virtual destructor is emulated? -- it is just really virtual. Or not...
--SA
pasztorpisti at 23-Sep-12 13:01pm
   
Sorry, made a mistake in my previous post and wrote 'constructor' instead of 'destructor'. Anyway if we are talking about the virtual destructor than everything is OK, if we speak of calling a normal virtual function from the destructor then I think the problems I described might persist.
Sergey Alexandrovich Kryukov at 23-Sep-12 14:35pm
   
I think we came to the conclusion...
--SA
pasztorpisti at 23-Sep-12 13:20pm
   
Sorry, misunderstood one of your post, the virtual destructor is fine just as your solution!
Sergey Alexandrovich Kryukov at 23-Sep-12 14:45pm
   
And now we really came to the conclusion. :-)
 
Thank you very much for this useful discussion. Despite of a number of confusions, it was really interesting to me and I think pretty fruitful.
 
-SA
pasztorpisti at 23-Sep-12 14:47pm
   
It was a pleasure. :-)
Sergey Alexandrovich Kryukov at 23-Sep-12 14:57pm
   
Mine, too.
Cheers,
--SA
pasztorpisti at 23-Sep-12 12:23pm
   
BTW, totally aggree with the .Net/Delphi approach. Unfortunately there is no 100% good solution to this problem because the object is vulnerable during construction/destruction for example if we pass its this pointer to the outside world. Despite this I would like to see some static checks in the compilers that warn about virtual calls from constructors/destructors even if those calls are indirect.
Sergey Alexandrovich Kryukov at 23-Sep-12 12:30pm
   
The difficulties certainly take place, and even in destructors, but not in this case. (And by they way, direct or indirect call, does not matter.) Static checks and warnings would hardly help much. In many cases, this is needed and "safe" (only if the situation is thoroughly though through), then what you are going to do? Ignore warnings?.. It makes things more complex then they would have to be. There are many more dangerous situations in C++ which are not warned. I always though that C++ is full of flaws. And the "fact" that it can work with low level better is a total myth. For example, Pascal does 100% of all of that low level but in a safer manner, same goes about Ada...
--SA
pasztorpisti at 23-Sep-12 12:36pm
   
Totally agree, I just had a pointless war about the deficiencies of C++ and usefulness of tools like Delphi on a message board, you might have read it. I wasn't afraid of the wolves and went to the forest for the second time... :-) Sometimes I question even the "optimal" nature of C++ as a high level language since a lot of things in it (like pointers/aliasing) make the work for modern optimizers much harder. Agree about pascal/delphi too, dlephi generated good code and compiled thousand times faster, I loved iterating with its superb IDE!!!
Sergey Alexandrovich Kryukov at 23-Sep-12 12:44pm
   
Me too. I see some fallacies in .NET as well, but they are much more delicate to include them in discussion here.
 
Perhaps I could not agree that the pointers could be removed from programming somehow to make things safer -- but they should be on certain lower level. If there is no a level of managed pointers/references, regular pointers would be everywhere, but it would not have to destroy smooth and safe programming process, as proven by Borland with Turbo/Borland/Object/Delphi Pascal (I personally started with Turbo... after dealing with other languages; think that I gained a lot in productivity).
--SA
pasztorpisti at 23-Sep-12 13:09pm
   
The Delphi ide is a superb ide like the C# env to create certain things. Tooling is very-very important for programmers too - I understood the importance of tooling on a much higher level in the games industry that involves much more complex logistics and workflows. Many kind of task can be parallelized for example when you create content/data and the tools can be a heavy multiplier on the productivity of your people no matter how good they are. The same is true for coders. The faster the iterations are the erlier you can finish the product with less bugs/better quality.
Sergey Alexandrovich Kryukov at 23-Sep-12 14:40pm
   
I feel the same way, but...
 
As to the tooling: it depends on relative values. I couple of cases, when I needed to learn new languages which had no IDE, I created my own IDE (for the first time, in Delphi 2.0, if I'm not much mistaken) which launches the build, and even presents errors/warning the way that the click on the item selected the issue in the source code. It's hard to believe, but that language without IDE was Java, at that time. I've done the same thing with something else as well.
 
If you have some burning interest to do something, you will create some tooling to proceed with enough comfort...
 
--SA
pasztorpisti at 23-Sep-12 14:47pm
   
In the games industry its a typical problem to balance out how much time do you spend on developing your tools (instead the actual game) to finish the actual project the earliest possible. :-) A lot of times it obviously pays off to invest time to your tools even if its a "use once then throw away" tool!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Advertise | Privacy | Mobile
Web02 | 2.8.140926.1 | Last Updated 21 Sep 2012
Copyright © CodeProject, 1999-2014
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100