Of courseQuote:
Originally posted by Paul McKenzie
Hopefully, you are referring to oktronic's hilarious diatribe against the C++ library.
Printable View
Of courseQuote:
Originally posted by Paul McKenzie
Hopefully, you are referring to oktronic's hilarious diatribe against the C++ library.
You say it works like a champ, but if another of your colleagues do this, then you'll be down for the ten count.
Say I want to add more customization to your string_vector. There is nothing stopping me (except maybe a big warning by you in big bold letters) to not derive my own classes and do some polymorphic work.Code:class MyCustomizedStringVector: public string_vector
{
//..
virtual ~MyCustomizedStringVector() { }
virtual int CustomizedFunc();
};
class AnotherCustomizedStringVector : public MyCustomizedStringVector
{
//...
virtual int CustomizedFunc();
};
int main()
{
string_vector *pMyVect;
if ( do_some_customizations )
pMyVect = new MyCustomizedStringVector;
else
pMyVect = new AnotherCustomizedStringVector;
//...
delete pMyVect;
}
Of course, this code leads to undefined behavior since the base class vector<string> does not have a virtual destructor. Even if me, the lowly user of your class, have a virtual destructor in my own class, it would not prevent the undefined behavior from occuring.
So what it all boils down to is that you put (whether you mean to or not) the same restrictions on usage of your class -- you shouldn't derive from it. Sounds familiar, doesn't it?
Again, there is no reason, even in your real-world example, to derive from vector<string>. The only thing you needed to do was to make vector<string> a member instead of deriving.
Regards,
Paul McKenzie
STL objects do not declare virtual destructors so if you derive from an STL object and that object is ever destroyed by a reference to base then your derived destructor never gets called. Also, since users of STL including the library know that you're not supposed to derive from STL objects their is a good chance that someone elses code will copy by value your derived class causing it to be silced. Virtual destructors are not provided by STL because there is a footprint/run-time overhead associated with them. The first time you add a virtual function you get _vtbl overhead regardless of whether or not anyone derives from you but often the compiler can un-virtualize the actual calls. Since the standard does not assume optimizing compilers and one of the standard's principles is "you do not have to pay for what you don't use" - they opt'd not to provide virtual destructors. Exception specifications have a similar overhead issue.Quote:
Why? Is it just bad form? Is there something deeper that I'm missing? I'm not having any problems, and I don't expect to.
In my earlier posts I was not discounting inheritence. Inheritance is the only way to manipulate polymorphic types at run-time and still ensure full type safety. But, many times you do not need the subtype polymorhism - parameterized polymorphism is adaquate. Subtype polymorphism is never faster than parameterized polymorhism. Also, parameterized polymorphism allows (through specialization) generation of optimal code/algorithms for each type while subtyping requires a single common algorithm for all types (I mean the code manipulating the polymorphic type - not internal overridden member functions). Even when optimal code is constructed for each derived type (through virtual member functions and overridden out-of-body functions) there is a cost to the dynamic dispatch.
All of this makes templates (STL included) a much more attractive option to inheritance when efficency matters even if inheritance is easier. Inheritance requires and creates higher cohesion between design concepts - this in turn increases the impact of design changes to the overall system structure which leads often to higher maintenance costs.
None of this might seem like that big a deal - inheritance works why don't we just use it? When timeliness doesn't matter this is probably a workable approach (yet still suboptimal). But I primarly design hard real-time systems. As such, I almost never use inheritance if templating is sufficent. Hense, I would never use MFC if STL is sufficent.
Well, maybe this is not the time or place to ask why, but, why?Quote:
Originally posted by Paul McKenzie
You say it works like a champ, but if another of your colleagues do this, then you'll be down for the ten count.
The code you wrote appeared to me that it should work. I'm not saying I don't believe, you, but... I dunno - maybe I'm just missing something fundamental. If I wanted to do what you described (derive and derive again), I would have attempted it the same way you did up there, and I would have expected it to work properly.
I read your explaination about virtual destructors, and undefined behavior, but I still don't see the why. It still appears to me that this sort of thing should work.
Maybe there's an article or something somewhere that would go into why this won't work?
I thought about that, but in the interest of saving time, I didn't want to write a dozen wrapper functions like push_back, clear, size, et. al. And I didn't want to expose a public vector object.Quote:
Originally posted by Paul McKenzie
Again, there is no reason, even in your real-world example, to derive from vector<string>. The only thing you needed to do was to make vector<string> a member instead of deriving.
Plus, as I said, I'm used to MFC where deriving your classes from the provided library is par for the course. It never dawned on me that I shouldn't derive STL classes until I stumbled across this thread.
Fair enough.Quote:
Originally posted by mclark
STL objects do not declare virtual destructors so if you derive from an STL object and that object is ever destroyed by a reference to base then your derived destructor never gets called.
But suppose my destructor does nothing.
Wouldn't that, then, remove this restriction - at least on my class. It's just another container for my end user (which happens to be me, anyway) and one that behaves exactly as he'd expect, with all the familiar methods and operators, with the additional goodies, I put in there for him - and if he knows better than to inherit from the STL anyway (ha-ha), he shouldn't have any problem not inheriting from my class as well, right?
So when I add that virtual destructor, I basically defeated this purpose - and since my dtor does nothing anyway, I should just 86 it altogether.Quote:
Originally posted by mclark
The first time you add a virtual function you get _vtbl overhead regardless of whether or not anyone derives from you but often the compiler can un-virtualize the actual calls.
Quote:
Originally posted by Paul McKenzie
Code:class MyCustomizedStringVector: public string_vector
{
//..
virtual ~MyCustomizedStringVector() { }
virtual int CustomizedFunc();
};
class AnotherCustomizedStringVector : public MyCustomizedStringVector
{
//...
virtual int CustomizedFunc();
};
int main()
{
string_vector *pMyVect;
if ( do_some_customizations )
pMyVect = new MyCustomizedStringVector;
else
pMyVect = new AnotherCustomizedStringVector;
//...
delete pMyVect;
}
Paul, I don't see a problem here.
A problem will occur only if one tries to do this:
Toxick's class string_vector does have a virtual destructor, and one may derive from it.Code:...
std::vector<std::string> *var = pMyVect;
delete var;
...
Therefore
should be safe to execute.Code:string_vector *pMyVect;
...
pMyVect = new AnotherCustomizedStringVector;
...
delete pMyVect;
The same if you do like this:
As long as you do not cast to hello for deletion (as delete (hello *)worldobject;), it will work fine.Code:struct hello
{
int a,b,c;
};
class world : public hello
{
//...
virtual ~world() {/*... */ }
//...
};
Probably I don't understand something?
Yes, then this is safe as long as you don't add any member data. But since, you've not added any member data and you can't override the inherited functionality (because it's not virtual and you've chosen public inheritance instead of private with forwarding functions - cuz as you said you don't want to write all the forwarding crap) then the only thing you can do is add additional utility functions. But then, those could just as easily been added as normal (non-member) functions that operate on the STL container (unless you're trying to gain access to private members - which are implementation specific and so non-portable anyways).Quote:
But suppose my destructor does nothing.
So, you can get the same effect by using normal functions, which in turn could be templatized and made to work with any vector<T> or container type (ala STL algorithms).
If what you intended to do was somehow restrict the use of the STL type, you cannot do so using public inheritance nor can you stop the user from converting your derived class back to the base - eliminating access to all of your utility functions.
Using the normal function approach may net seem as appealing because it seems less 'OO' but it is a better design.
Well, not to step on mclark's toes, but if you do not define a virtual destructor in your base class, the behavior is undefined if you delete through a base pointer. It may work, it may fail, you don't know. Most of the time, the behavior is to not call the derived class destructor, but again, you don't know.
This is clearly specified in the ANSI/ISO C++ specification. Here is the clause in 5.3.5.5
So there you have it. Your code (or my utilization of your code), leads to undefined behavior. The static type is vector<string>, and the dynamic types are MyCustomizedVectorString, AnotherCustomizedVectorString. Since the static type does not have a virtual destructor, calling delete is undefined behavior.Quote:
In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the
static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual
destructor or the behavior is undefined.
Regards,
Paul McKenzie
Sure you can derive from it. The problem is that the behavior is still undefined. The virtual destructor must be in the top-most base class. The top-most class is vector<string> -- it does not have a virtual destructor.Quote:
Originally posted by gyohng
Paul, I don't see a problem here.
A problem will occur only if one tries to do this:
Toxick's class string_vector does have a virtual destructor, and one may derive from it.Code:...
std::vector<std::string> *var = pMyVect;
delete var;
...
You are using what you think should work, instead of investigating what the rules are of C++ (which I specified in my previous post in clause 5.3.5.5).
Regards,
Paul McKenzie
I stand corrected - my bad for not checking before I spoke. I'm a lot more careful with my code than with my words. :)Quote:
Originally posted by Paul McKenzie
Well, not to step on mclark's toes, but if you do not define a virtual destructor in your base class, the behavior is undefined if you delete through a base pointer.
No, I didn't. The only thing I added was 2 operators, and serialization functions.Quote:
Originally posted by mclark
Yes, then this is safe as long as you don't add any member data.
Which is exactly what I did. So it sounds to me that I'm pretty much in business with my classes as they are, and anyone who uses them in the future should not run into any snags along the way.Quote:
Originally posted by mclark
But since, you've not added any member data and you can't override the inherited functionality (because it's not virtual and you've chosen public inheritance instead of private with forwarding functions - cuz as you said you don't want to write all the forwarding crap) then the only thing you can do is add additional utility functions.
Honestly, after reading all you wrote, the fact that my classes work the way they're intended is dumb luck - and not by any design of my own. :)
Not really. I just want to use an object that looks, smells, acts, and IS a vector - but one to which I can pass an fstream object for loading and saving of the vector elements. (And one that I can use += for appending new elements.)Quote:
Originally posted by mclark
But then, those could just as easily been added as normal (non-member) functions that operate on the STL container (unless you're trying to gain access to private members - which are implementation specific and so non-portable anyways).
I know I could do all that through external (or existing) functions, and being a C programmer at heart, that was my first instinct. But I'm trying to blossom into an OO programmer, and I thought that was what OO was all about. Reusing stuff that's already built.
I restricted the use of the STL type, not a whit.Quote:
Originally posted by mclark
If what you intended to do was somehow restrict the use of the STL type, you cannot do so using public inheritance nor can you stop the user from converting your derived class back to the base - eliminating access to all of your utility functions.
In fact, the very reason I derived from the STL type was because I wanted to use all the functionality that it currently contains. However, if the end user wants to cut himself off at the knees and cast up to a class that has the original functionality, that's fine by me. My new methods are not vital at all to the behavior of the vector itself - they are merely quick and painless ways of building in the functionality of serializing object data.
Well, in my case it is... So, suppose I go all the way back to my original code up there, and delete the line: virtual ~string_vector() {}Quote:
Originally posted by Paul McKenzie
Most of the time, the behavior is to not call the derived class destructor, but again, you don't know.
Because my dtor is empty anyway. It adds or removes not a thing.
One of the principles of good OO is that an obect's interface be minimal. If an operation or algorithm can be implemented in terms of already existing public interfaces; then the new operation or algorithm should itself NOT be a member but instead a stand-alone function. In the long run this makes a class heirarchy much more extensible. The approach you took is understandable - but not correct. It is often refered to as a 'fat' interface.Quote:
Originally posted by Toxick
But I'm trying to blossom into an OO programmer, and I thought that was what OO was all about. Reusing stuff that's already built.
If two operations are circularly dependent, then the simpler of the two (most generalizable to subtyping) should be chosen as an interface member. If neither is widely generalizable, then neither should be members. Instead, one should be a 'friend' and the other implemented in terms of the friend.
Doing otherwise means that the heirarchy is self-limiting. Defining subtypes (IS-A relationships) is artifically restricted to concepts who are both IS-A and operation-applicable.
Good OO often means knowing what's not a object.
Paul,
The clause says that it is safe. And your code will work.
Indeed, static type pointer, which is string_vector, has VIRTUAL DESTRUCTOR.
We are doing:
delete ( string_vector * ) obj ;
where
class string_vector : public vector<string>
{
...
----> virtual ~string_vector() { } <----
...
}
if obj has type MyCustomizedStringVector* , then
- the dynamic type is MyCustomizedStringVector
- the static type is string_vector
and the clause is correct, because MyCustomizedStringVector and string_vector BOTH do have virtual destructors.
As long as code is guaranteed not to delete by std::vector<std::string>* type, then the execution is guaranteed to be proper in this case.
Again, as long as we only delete by ANY type that has virtual destructor (meant as static (compile-time) type in the Standard clause), the delete operator is guaranteed to work properly.
Clause does not say "all of the chain should have virtual destructors", clause says "type we delete by should be somewhere in the chain BEFORE the runtime type, and it should have virtual destructor". Clause does not mean the base class of the operand static pointer type should have virtual destructor. It only says that the operand static type itself should have virtual destructor, and not the parent of the operand static type.
To avoid misunderstanding, here is it again:
static_type *obj = new dynamic_type ( ) ;
...
delete obj ;
Thanks,
George.
But it still doesn't take away from the fact that deleting string_vector through a pointer-to-base is undefined behavior. In fact, there is no rule at all in ANSI C++ that states that "if you have no members defined, you're OK if you delete your object". Undefined behavior means just that -- undefined. Whether or not your class is empty, it makes no difference.Quote:
Originally posted by Toxick
Well, in my case it is... So, suppose I go all the way back to my original code up there, and delete the line: virtual ~string_vector() {}
Because my dtor is empty anyway. It adds or removes not a thing.
Don't be surprised one day when your code crashes, and you'll be scratching your head as to why the crash occurs. It's the same type of situation where a C++ programmer uses memcpy() for a non-POD object, and then suddenly when they port their program or upgrade the compiler, their program ceases to work. It happened to me a month ago -- a programmer used memcpy() to initialize a struct that contained a std::string, and the program "worked" under VC 6.0. When ported to C++ builder (we need to create libraries for both compilers), the code crashed.
Regards,
Paul McKenzie