To STL or not to STL, that is the question... - Page 6
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 6 of 19 FirstFirst ... 345678916 ... LastLast
Results 76 to 90 of 280

Thread: To STL or not to STL, that is the question...

  1. #76
    Join Date
    Nov 2002
    Location
    Los Angeles, California
    Posts
    3,863
    Originally posted by Paul McKenzie
    Hopefully, you are referring to oktronic's hilarious diatribe against the C++ library.
    Of course
    Wakeup in the morning and kick the day in the teeth!! Or something like that.

    "i don't want to write leak free code or most efficient code, like others traditional (so called expert) coders do."

  2. #77
    Join Date
    Apr 1999
    Posts
    27,427
    You say it works like a champ, but if another of your colleagues do this, then you'll be down for the ten count.
    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;
    }
    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.

    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
    Last edited by Paul McKenzie; November 21st, 2003 at 11:11 AM.

  3. #78
    Join Date
    Nov 2003
    Location
    Pasadena, CA
    Posts
    48
    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.
    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.

    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.
    The views expressed are those of the author and do not reflect any position taken by the Goverment of the United States of America, National Aeronautics and Space Administration (NASA), Jet Propulsion Laboratory (JPL), or California Institute of Technology (CalTech)

  4. #79
    Join Date
    Sep 2002
    Location
    Tucson, Arizona
    Posts
    26
    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.
    Well, maybe this is not the time or place to ask why, but, why?

    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?

    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.
    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.

    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.
    No animals were harmed in the transmission of this message. However, a good number of electrons were somewhat inconvenienced.

  5. #80
    Join Date
    Sep 2002
    Location
    Tucson, Arizona
    Posts
    26
    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.
    Fair enough.

    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?

    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.
    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.
    No animals were harmed in the transmission of this message. However, a good number of electrons were somewhat inconvenienced.

  6. #81
    Join Date
    Nov 2003
    Posts
    7
    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:

    Code:
    ...
    std::vector<std::string> *var =  pMyVect;
    
    delete var;
    ...
    Toxick's class string_vector does have a virtual destructor, and one may derive from it.

    Therefore
    Code:
       string_vector *pMyVect;
    ...
       pMyVect = new AnotherCustomizedStringVector;
    ...
       delete pMyVect;
    should be safe to execute.



    The same if you do like this:

    Code:
    struct hello
    {
        int a,b,c;
    };
    
    class world : public hello
    {
    //...
        virtual ~world() {/*... */ }
    //...
    };
    As long as you do not cast to hello for deletion (as delete (hello *)worldobject, it will work fine.


    Probably I don't understand something?
    http://www.yohng.com/

  7. #82
    Join Date
    Nov 2003
    Location
    Pasadena, CA
    Posts
    48
    But suppose my destructor does nothing.
    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).

    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.
    The views expressed are those of the author and do not reflect any position taken by the Goverment of the United States of America, National Aeronautics and Space Administration (NASA), Jet Propulsion Laboratory (JPL), or California Institute of Technology (CalTech)

  8. #83
    Join Date
    Apr 1999
    Posts
    27,427
    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
    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.
    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.

    Regards,

    Paul McKenzie

  9. #84
    Join Date
    Apr 1999
    Posts
    27,427
    Originally posted by gyohng
    Paul, I don't see a problem here.

    A problem will occur only if one tries to do this:

    Code:
    ...
    std::vector<std::string> *var =  pMyVect;
    
    delete var;
    ...
    Toxick's class string_vector does have a virtual destructor, and one may derive from it.
    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.

    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

  10. #85
    Join Date
    Nov 2003
    Location
    Pasadena, CA
    Posts
    48
    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.
    I stand corrected - my bad for not checking before I spoke. I'm a lot more careful with my code than with my words.
    The views expressed are those of the author and do not reflect any position taken by the Goverment of the United States of America, National Aeronautics and Space Administration (NASA), Jet Propulsion Laboratory (JPL), or California Institute of Technology (CalTech)

  11. #86
    Join Date
    Sep 2002
    Location
    Tucson, Arizona
    Posts
    26
    Originally posted by mclark
    Yes, then this is safe as long as you don't add any member data.
    No, I didn't. The only thing I added was 2 operators, and serialization functions.

    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.
    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.


    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.

    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).
    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.)

    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.

    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.
    I restricted the use of the STL type, not a whit.

    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.
    No animals were harmed in the transmission of this message. However, a good number of electrons were somewhat inconvenienced.

  12. #87
    Join Date
    Sep 2002
    Location
    Tucson, Arizona
    Posts
    26
    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.
    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.
    No animals were harmed in the transmission of this message. However, a good number of electrons were somewhat inconvenienced.

  13. #88
    Join Date
    Nov 2003
    Location
    Pasadena, CA
    Posts
    48
    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.
    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.

    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.
    The views expressed are those of the author and do not reflect any position taken by the Goverment of the United States of America, National Aeronautics and Space Administration (NASA), Jet Propulsion Laboratory (JPL), or California Institute of Technology (CalTech)

  14. #89
    Join Date
    Nov 2003
    Posts
    7
    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.
    Last edited by gyohng; November 21st, 2003 at 12:52 PM.
    http://www.yohng.com/

  15. #90
    Join Date
    Apr 1999
    Posts
    27,427
    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.
    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.

    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

Page 6 of 19 FirstFirst ... 345678916 ... LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center