Thread: inheritance

  1. #1
    Registered User
    Join Date
    May 2009
    Posts
    242

    inheritance

    I'm starting a new thread because I'm beginning to think that I was way overthinking several inheritance aspects that the compiler takes care of (the guy who said I'd get a compiler error in certain dangerous cases was absolutely right).

    Here's my attempt to get back to an overview of the important things:

    1) If your class is going to be a base class, just routinely define your destructor as virtual.

    2) Be sure actually to destroy everything as necessary if you use dynamic memory allocation in the constructors.

    3) There are a few syntactical things to remember in certain cases (often constructors and assignment operator) due to the fact that you can't directly access the private members of your base class. But here we're really just dealing with finding language to get the base class members assigned properly.

    4) Declare as virtual other functions in your base class that will need to be redefined in some derived classes.

    Pretty much everything else seems to me to follow from the "is-a" relationship. So, if that relationship is right, things should normally work. The "difficulties" here are really more syntactical than substantive (in the sense that you're doing anything dangerous to memory).

    Does that sound about right?

  2. #2
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    2) Be sure actually to destroy everything as necessary if you use dynamic memory allocation in the constructors.
    And the best way to do this is to have a separate manager object for every single resource.

    There are a few syntactical things to remember in certain cases (often constructors and assignment operator) due to the fact that you can't directly access the private members of your base class. But here we're really just dealing with finding language to get the base class members assigned properly.
    In my experience, inheritance hierarchies and value semantics (where you want to implement instead of forbid assignment) don't mix. In other words, if your class is part of a public hierarchy, you probably want to make it noncopyable.

    Otherwise, this sounds right.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  3. #3
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Aisthesis View Post
    1) If your class is going to be a base class, just routinely define your destructor as virtual.
    As a rule of thumb, what you say often works. But, as a general statement, I disagree - it is not always applicable.

    The only circumstance where a virtual destructor is necessary is if some variation of "delete object;" is performed, where object is a pointer to base that points to an instance of a derived class.

    In practice, if base class pointers or references are going to be used polymorphically, then it is often a fair bet that objects will be deleted through a pointer or reference to base. In that case a virtual destructor is needed.

    However, as a counter-example to your statement, let's say we have a base class named Animal and a derived class named Horse. This code;
    Code:
    //   definitions of Animal and Horse
    int main()
    {
        Horse horse;
        Racetrack track;
        horse.run(track);
    }
    does not require Animal to have a virtual destructor.

    It all comes down to what assumptions the base class implementer can make about how the derived class will be used. If those assumptions are flawed, the derived class implementers often need to do "syntactic" workarounds.

    Quote Originally Posted by Aisthesis View Post
    2) Be sure actually to destroy everything as necessary if you use dynamic memory allocation in the constructors.
    While this true, it is only part of the picture.

    It is advisable in C++ to avoid using dynamic memory allocation of arrays at all and use standard containers instead. And, where possible, it is advisable to use smart pointers to manage instances of single dynamically allocated objects, as that reduces chances of forgetting to destroy something dynamically allocated.

    CornedBee is also perfectly correct in pointing out that it is good practice to ensure every resource has a single manager object type. Again, because this reduces chances of forgetting to destroy something - the smart pointer and standard containers I mentioned above are useful examples of this.

    One other thing you're neglecting is that memory is not the only resource that needs to be managed: other system resources such as file handles and (in multithreaded code) threads, mutexes, critical sections also need to be managed in the same manner. No point in managing memory beautifully if your program falls over because you have locked files unintentionally so they cannot be reopened as long as your program is running.
    Quote Originally Posted by Aisthesis View Post
    3) There are a few syntactical things to remember in certain cases (often constructors and assignment operator) due to the fact that you can't directly access the private members of your base class. But here we're really just dealing with finding language to get the base class members assigned properly.
    I disagree with this one. If a derived class needs to directly access private members of a base class in any way, then the design and implementation of the base class is broken. Specifically, the base class should implement all functionality (constructors, assignment operators, other member functions) so that derived classes should never need to access its private members.

    If a base class provides a proper working set of constructors and assignment operators, then derived classes also rarely need to implement constructors and assignment operators of their own - the compiler-supplied defaults will actually be sufficient in most cases. Exceptions to this are, often, a sign that the base class design and implementation is incomplete.
    Quote Originally Posted by Aisthesis View Post
    Pretty much everything else seems to me to follow from the "is-a" relationship. So, if that relationship is right, things should normally work.
    You're missing the fat that only public inheritence should be a "is a" relationship. Private inheritence and protected inheritence are used to represent "implemented using" relationships. The statements you're making don't quite apply to those relationships.
    Quote Originally Posted by Aisthesis View Post
    The "difficulties" here are really more syntactical than substantive (in the sense that you're doing anything dangerous to memory).
    Again, I don't agree.

    Every time where I've seen a programmer get into trouble with a class hierarchy doing "anything dangerous to memory", the root cause in code is often associated with "syntactical" hackery.

    Turning that around, derived classes using syntactic "difficulties" to implement constructors, assignment operators, or other functions are more likely to have "substantive" errors that are hard to track down - and these are often the types of problems that result in cryptic bug reports from end users as they are hard for programmers to detect.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by grumpy
    As a rule of thumb, what you say often works. But, as a general statement, I disagree - it is not always applicable.
    I think that you should consider the word "routinely". Aisthesis is talking about a rule of thumb, and in that it is certainly correct.

    Quote Originally Posted by grumpy
    It all comes down to what assumptions the base class implementer can make about how the derived class will be used. If those assumptions are flawed, the derived class implementers often need to do "syntactic" workarounds.
    Hence, it is wise to "routinely" declare a base class destructor as virtual. It may be unnecessary, but the assumption that an object of a derived class will be destroyed via a base class pointer tends to be more applicable than the assumption that an object of a derived class will never be destroyed via a base class pointer.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Don't get me wrong, laserlight, I agree. The advantage of rules of thumb is that they are often applicable, and therefore usually work well in practice. The problem with rules of thumb is that they often become stated as gospel, which causes all sorts of problems in circumstances where they don't work.

    I do, however, consider that rules of thumb which encourage or implicitly accept syntactic workarounds for design deficiencies are a particularly poor idea. That's why my strongest objection is to the third rule proposed by aisthesis ..... One rule of software engineering needs to be "put more effort in to iterate your design to minimise design deficiencies rather than working around them, and do that as early and as often as needed". Pragmatically, it will sometimes be necessary to use syntactic workarounds (after all, programming languages have design trade-offs) but a rule of thumb which encourages that is not targeting the real concern.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  6. #6
    Registered User
    Join Date
    May 2009
    Posts
    242
    grumpy, I think your disagreement on 3 is more a difference on what you and I consider "syntactical difficulties." For me, certain things are new that you did years ago. What I'm talking about as being, let's say more neutrally, "different" than what I had seen previously are things like this (using the exercise I elaborated in the other thread):

    Cd is a base class for Classic and has various private member fields and a constructor with prototype
    Code:
    Cd(char * s1, char * s2, int n, double x);
    And all this does is just assign the values given appropriately to the private member fields.

    Now for Classic we have the prototype
    Code:
    Classic(char * s1, char * s2, int n, double x, char * w);
    and Classic adds exactly one private member, which is char work[50];

    Well, the constructor for Classic can't access the private members of Cd, so the implementation goes:
    Code:
    Classic::Classic(char * s1, char *s2, int n, double x, char * w)
       :Cd(s1, s2, n, x)
    {
        // code for copying w to work[50] array
    }
    The extra ":Cd(s1, s2, n, x)" is what I was talking about (similarly for assignment a line like Cd:perator=(d); in the code looks weird the first time you see it--which for me was only the last few days).

    Both of these are used to set values for the private members of the base class. But, I'm assuming since the book presents them just as "the way to do it," they presumably aren't the kind of syntactical gymnastics that you're talking about that give reason to believe that you're not designing your classes properly.

    Also, yes, I'm only talking about public inheritance, as I haven't been introduced to private yet, although I did see that laser mentioned it in another thread. I'm guessing that will come in due time.

    Another thing that isn't familiar to me yet are corned Corned's "manager objects." I figure that's something that I'll also get to in due time but wondered at this stage what the context is where that tends to be introduced--often helps me with the more advanced stuff if I at least have some general orientation beforehand ... Will it come up in "first year C++" material usually?

  7. #7
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Aisthesis View Post
    Cd is a base class for Classic and has various private member fields and a constructor with prototype
    Code:
    Cd(char * s1, char * s2, int n, double x);
    And all this does is just assign the values given appropriately to the private member fields.

    Now for Classic we have the prototype
    Code:
    Classic(char * s1, char * s2, int n, double x, char * w);
    and Classic adds exactly one private member, which is char work[50];

    Well, the constructor for Classic can't access the private members of Cd, so the implementation goes:
    Code:
    Classic::Classic(char * s1, char *s2, int n, double x, char * w)
       :Cd(s1, s2, n, x)
    {
        // code for copying w to work[50] array
    }
    The extra ":Cd(s1, s2, n, x)" is what I was talking about (similarly for assignment a line like Cd:perator=(d); in the code looks weird the first time you see it--which for me was only the last few days).
    OK; I don't really view your example as syntactic difficulty. I view your class Classic's constructor implementation as explicitly controlling how the base class constructor is invoked. If you want classes to work correctly, it is necessary to specify exactly how they are constructed, among other things. That is not a syntactic workaround: it is a design concern. There is nothing syntactically stopping you invoking another constructor of Cd if you need to - the only constraint is that you must be sure the Cd constructor that you invoke works as you intend - either by how you implement it, or (in the case of classes written by other people) by reading the documentation and writing test cases to test/confirm/deny your beliefs.

    Quote Originally Posted by Aisthesis View Post
    Both of these are used to set values for the private members of the base class. But, I'm assuming since the book presents them just as "the way to do it," they presumably aren't the kind of syntactical gymnastics that you're talking about that give reason to believe that you're not designing your classes properly.
    An example (certainly not the only one) of syntactical gymnastics of concern are cast operations (whether the C style cases, or C++ _cast operations). If you're being forced to use such operations to implement derived class constructors, there is - almost always - something much deeper lurking in your design waiting to cause you troubles.

    I have seen class hiearchies where the programmer relied on various conversion operations, and using _cast operators to stop the compiler complaining. With a bit more effort into design, those gymnastics were not needed. But the programmers involved actively resisted doing such redesign - in one case, played considerable organisational politics to defend his practice.

    There are many corners of C++ syntax (some caused by compatibility with C) and they are easily abused. My concern is that your third rule of thumb could be interpreted as a suggestion that such abuses are OK. And, practically, they are not.
    Quote Originally Posted by Aisthesis View Post
    Also, yes, I'm only talking about public inheritance, as I haven't been introduced to private yet, although I did see that laser mentioned it in another thread. I'm guessing that will come in due time.
    Fair enough. Just be aware that you'll need to revisit a few of your thoughts when you turn your hand to that. Not in a big way, but some of those small deltas become important.
    Quote Originally Posted by Aisthesis View Post
    Another thing that isn't familiar to me yet are corned Corned's "manager objects." I figure that's something that I'll also get to in due time but wondered at this stage what the context is where that tends to be introduced--often helps me with the more advanced stuff if I at least have some general orientation beforehand ... Will it come up in "first year C++" material usually?
    If you look up the RAII (Resource Acquisition Is Initialisation - or "Initialization" if you're American) principle, you will find the core practical underpinnings of the "manager objects" that CornedBee mentioned.

    It's not actually a highly advanced or difficult concept, but it is a basic one that - once you grasp it - will make you a better programmer. Even if it is rarely covered in first year courses.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  8. #8
    Registered User
    Join Date
    May 2009
    Posts
    242
    Thanks, grumpy, the RAII stuff sounds very interesting. Just checked out the Wikipedia article as intro, and that has a few references. Didn't find the specific term in Stroustrup (C++ Programming Language, 2000), but appendix E on "Standard Library Exception Safety" seems to have a lot to do with it, if I'm understanding the term properly (?).

  9. #9
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by Aisthesis View Post
    Didn't find the specific term in Stroustrup (C++ Programming Language, 2000), but appendix E on "Standard Library Exception Safety" seems to have a lot to do with it, if I'm understanding the term properly (?).
    You're understanding the term correctly: RAII, as a technique, helps ensure resources are properly recovered between the code that thows an exception and the code that catches it. Being able to properly manage and control resource recovery is a key to a number of exception safety guarantees specified for standard library functions - as Stroustrup describes in that Appendix E.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. conditional inheritance?
    By arcaine01 in forum C++ Programming
    Replies: 17
    Last Post: 09-04-2009, 04:12 PM
  2. Replies: 16
    Last Post: 06-08-2009, 03:03 PM
  3. Multiple Inheritance - Size of Classes?
    By Zeusbwr in forum C++ Programming
    Replies: 10
    Last Post: 11-26-2004, 09:04 AM
  4. inheritance and performance
    By kuhnmi in forum C++ Programming
    Replies: 5
    Last Post: 08-04-2004, 12:46 PM
  5. Inheritance vs Composition
    By Panopticon in forum C++ Programming
    Replies: 11
    Last Post: 01-20-2003, 04:41 AM