Thread: Inheritance and Arrays

  1. #16
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708

    Post hmm...

    First of all you should test your ideas on single objects - not as arrays of pointers to derived classes. You should know better than that. The idea behind the virtual function is that whenever base class code calls it, immediately it jumps to the derived version. Simple as that. Elad's example was good but he forgot to make the Circle:raw virtual also, important for derived classes of the circle and so on. But anyhoo, you've got it now.


    Code:
    struct ABC {
    virtual ABC(){self() = this;}
    virtual ~ABC(){}
    ABC * self(){return _self;}
    virtual void talk(){cout << "ABC" << endl;}
    private :
    ABC * _self;
    };
    
    struct DEF : ABC {
    virtual DEF(){}
    virtual ~DEF(){}
    virtual void talk(){cout << "DEF" << endl;}
    };
    
    struct HIJ : ABC {
    // perhaps no ancestors for this one?
    HIJ(){} 
    ~HIJ(){}
    virtual void talk(){cout << "HIJ" << endl;} 
    };
    
    // 
    
    int main(){
    
    DEF def;
    ABC * t = def.self();
    t->talk();
    return 0;
    }

  2. #17
    Geek. Cobras2's Avatar
    Join Date
    Mar 2002
    Location
    near Westlock, and hour north of Edmonton, Alberta, Canada
    Posts
    113
    as a side note totally unrelated to this current post, I still think someone ought to make it so smilies are disabled inside code brackets

  3. #18
    Registered User
    Join Date
    Mar 2002
    Posts
    1,595
    It is my understanding that if a function is declared as virtual in the base class and another class is derived from that class, then the function in the derived class is virtual as well, by inheritance, and there is no need to declare the function virtual in the derived class (it isn't wrong to declare it virtual in the derived class, but it isn't necessary either). But I'm not always correct in my understanding, so look it up in your favorite reference.

  4. #19
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Originally posted by grib
    use std::vector<ABC *> v;

    to add a Derived1 v.push_back(new Derived1(/*args if needed*/));

    any calls on methods of ABC will call the Derived method iff those methods are virtual.

    If you need to use something that's not in the ABC you need to use dynamic_cast<Derived1 *>(v[3]); This will give you a pointer iff v[3] actually points to a Dervived1.

    Naturally, you need to rember to delete each element.
    I agree with the std::vector idea, but I'd take it a step further. Download a good supplimentary library, like Boost and use a vector of shared_ptr instead. Unlike std::auto_ptr, boost::shared_ptr is container-safe (and will almost certainly be included in the next revision of the standard library).

    The benefit here is that you never have to manually delete any pointers, and you get exception safety (as long as you don't try to instantiate a pointer as a temporary in a function call, you can't leak memory even if an exception is thrown).

    Arrays and pointers are the two most dangerous features of the language; they're where your memory leaks will occur. "When you learn C, you spend most of the time learning how to use arrays and pointers; when you learn C++, you spent most of the time learning how to avoid using them." This is not to suggest that you forget about learning pointers & arrays, but rather, that you learn them, and then learn their drawbacks as well.

    It is not easy to try to make allocating objects within an array of object pointers exception-safe (e.g. if you are allocating 100 objects, and object 53 throws an exception at creation, you need to destroy objects 1-52).

    For example, this is NOT exception safe:

    Code:
    // Assume MyObject** array is a member of MyClass
    
    MyClass::MyClass(){
      array = new MyObject* [100];  // Line A
      for (int i = 0; i < 100; i++)
        array[i] = new MyObject;  // Line B
    }
    Now suppose an exception occurs at Line B when i = 50. What happens?

    1) ~MyClass() will NOT be called, because the constructor didn't finish.
    2) The *variable* array (which is really a pointer to the first element of an array of pointers) is freed.
    3) The memory allocated in Line A (the array of pointers itself) is NOT freed.
    4) The 50 objects that did get constructed in Line B are not destroyed, and their memory is not freed.

    In this example, you leak 50*sizeof(MyObject) + 100 * sizeof(MyObject *), because you allocated 50 objects and 100 pointers that can't be freed.

    Note, it is certainly possible to make it exception safe, by catching the exception immediately, deleting all the objects that were successfully made, and then rethrowing the exception (assuming you want to abort the constructor when you can't allocate). But this can take up a lot of code -- the cleanup code would take up more space than the allocation code! Further, there's a more elegant way of achieving the same goal. The elegant way also has the benefit that it can't leak resources for other reasons -- e.g. a malformed destructor for the above method could also be a source of a resource leak, or a member function that did array operations, etc. You can think of it as plugging many possible leaks at once.

    The elegant way would be:

    Code:
    typedef boost::shared_ptr<MyObject> MyObjectPtr;
    typedef std::vector<MyObjectPtr> MyObjectVector;
    
    // assume MyObjectVector mov is a member of MyClass 
    MyClass::MyClass(){
      mov.reserve(100);             // Line A
      for (int i = 0; i < 100; i++)
        mov.push_back(MyObjectPtr(new MyObject));   // Line B
    }
    Assume the exception happens just as before. Now what happens?

    1) ~MyClass() will still not be called, because the constructor didn't finish. (which is good; destroying a half-built object is not a good idea)
    2) The variable mov is freed. Because mov itself finished construction, it is destroyed (its destructor is called and its memory is released).
    3) As mov is destroyed, each of the 50 smart pointers that constructed properly is deleted by vector's destructor.
    4) The memory (potentially) allocated in Line A is freed with the destruction of the vector object.
    5) When each of the smart pointers is deleted, its reference count drops to zero (it was the last pointer to that object) so it frees the memory of the 50 MyObject objects.

    Now, you have the same functionality as before, but you are protected against memory leaks, even when an exception is thrown partway through construction.

    This also has the added benefit that both the vector and the pointers "clean up after themselves", so you don't need to remember to destroy them.

    Do note, that your objects (of class MyObject) MUST NEVER throw an exception in their destructors. This is a general rule; destructors should never throw exceptions.

    Oh, there is one case in which shared_ptr cannot help you if an exception is thrown, and that is this case:

    Code:
    myFunction(MyObjectPtr(new MyObject),myFunction2());
    In this case, we know that "new MyObject" must be evaluated before MyObjectPtr's constructor, but compilers can do the call to myFunction2() anytime. So if your compiler executed in this order:

    1) new MyObject
    2) myFunction2()
    3) MyObjectPtr::MyObjectPtr()

    then you could leak resources if myFunction2() throws an exception. You should NEVER use this code like this, even if you can assure me that your compiler doesn't use that particular order of execution. The compiler is ALLOWED to do things in that order. Yes, there are 3 different orders that it could execute in, and that is the least likely for a compiler to use, but your code should work under ANY legal compiler, and the above order is legal if unusual.

    The safe way to do the above call is:

    Code:
    MyObjectPtr p(new MyObject);
    myFunction(p,myFunction2());
    Now the object is destroyed even if myFunction2() throws.

    By the way, the following code is safe, because the compiler has no freedom in order of executions: It must first call new, then MyObjectPtr's constructor, then vector::push_back(). There is no place for an exception to occur between the call to new and the call to the smart pointer's construction.

    Code:
     mov.push_back(MyObjectPtr(new MyObject));
    Do be warned now that shared_ptr isn't a magic bullet that stops all possible leaks. There are ways you can force a memory leak, even with shared_ptr; the main one is an object with a shared_ptr to itself (e.g. say you have a shared_ptr named A that points to an object B, which has a shared_ptr that is a copy of A). In that case, you'll always leak (B can't be destroyed until A's reference count is zero, but A's reference count can't be zero until B's destructor releases the pointer). Boost also provides a means of dealing with this (weak_ptr), for situations where B really needs to have a shared_ptr to itself.

    So, the bottom line of my advice is use shared_ptr (or a similar template class from another library) whenever it makes sense, but be careful, because it's still possible to make yourself leak memory unintentionally. Remember it's ALWAYS possible to leak, and nothing you do can prevent it, but you can minimize how the leaks can happen.
    Last edited by Cat; 06-06-2003 at 01:31 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. struct array's inheritance
    By emorrp1 in forum C Programming
    Replies: 8
    Last Post: 06-27-2008, 05:54 AM
  2. Object Arrays and Inheritance
    By AceHigh in forum C++ Programming
    Replies: 7
    Last Post: 07-28-2002, 04:08 AM