Archived

This forum has been archived. Please start a new discussion on GitHub.

Problem with smart pointers

There exists a problem when using Listeners(or any class containing a smart pointer to a parent-object and constructed in the scope of the parent-class) together with smart pointers.
The following code reproduces this problem:
#include <ios>
#include <IceUtil/Handle.h>
#include <IceUtil/Shared.h>

class X;

typedef IceUtil::Handle<X> XPtr;

class Listener : public virtual IceUtil::Shared {

private:
   XPtr _parent;

public:
   Listener(XPtr p) : _parent(p) {}
   ~Listener() {
      _parent = NULL;
   }

};

typedef IceUtil::Handle<Listener> ListenerPtr;

class X : public virtual IceUtil::Shared {

private:
   ListenerPtr _listener;
   
public:
   X() {
      _listener = new Listener(this);
   }

   ~X() {
      _listener = NULL;
   }

};

class Y : public virtual X {

public:

   Y(){
      throw "Exception in Constructor of Subclass";
   }
   ~Y(){}

};

typedef IceUtil::Handle<Y> YPtr;

int main(int argc, char* argv[]) {
   try {
      YPtr y = new Y;
   }catch(...) {
      std::cout << "Exception caught." << std::endl;
   }
   return 0;
}


When "YPtr = new Y;" is called, the constructor of X is called first. But the constructor of Y throws an exception resulting in a call to the destructor of X. Unfortunatly the only smart pointer pointing to the constructed object is the one inside the Listener-object, which is destructed as well. This results in a double call to the destructor of X and a segmentation fault.
As far as I see it, there exist only the following solutions to this problem:

1. Implementing the Listener without smart pointers, using native pointers. This might result in undefined behaviour or segmentation faults if it is not very carefully integrated with the smart pointers needed by Ice.
2. Implementing the constructors of all classes or at least all subclasses without the use of exceptions. For example "Y() throw() {...}" to make sure unexpected is called if an exception is thrown. This might be difficult, if, for example, certain error-conditions have to be explicitly tested.
3. Omitting the use of Listener-classes, which might put an additional constraint on the developed architecture.

I would be interested in any thoughts regarding this problem. For example: Did I overlook a solution? Or do you think, I overstate the importance of Listeners, as there is an aquivalent software-pattern not causing this kind of problem?

Thank you in advance

Gerald

Comments

  • mes
    mes California
    Hi,

    The IceUtil::Shared and IceUtil::SimpleShared classes provide the __setNoDelete member function for situations like this:
    Y::Y()
    {
        __setNoDelete(true);
        throw "Exception in Constructor of Subclass";
    }
    
    The __setNoDelete function sets a flag that prevents the object from being deleted when its reference count becomes zero.

    In the Ice internals we use the following technique:
    MyClass::MyClass()
    {
        try
        {
            __setNoDelete(true);
            // ... initialization that may raise an exception ...
            __setNoDelete(false);
        }
        catch(...)
        {
           // clean up ...
           __setNoDelete(false);
           throw;
        }
    }
    
    The try block disables deletion during initialization and re-enables it after initialization succeeds. In the case of an exception, the catch block cleans up (essentially undoing the initialization) and then enables deletion just prior to rethrowing the exception. In this example, the clean up step in the catch block ensures that no other references to the object exist, and therefore it is safe to allow IceUtil::Shared to delete the object when its reference count reaches zero.

    Hope that helps.
    - Mark
  • Thanks. That's perfekt.

    Gerald