문제

I have a composite pattern implementation, used for GUI components:

class CObject {
private:

  CObject * m_pParent;  
  CObjectContainer * m_pChildren;

  void private_foo() {
    this->foo();
    //Calls private_foo for each child in container.
    m_pChildren->foo();
  }

public:
  virtual void foo() {
    //empty for base class
  }

  virtual CObject * duplicate() {
    //Do duplication code
    return new CObject(*this);
  }

  virtual CObject * detach() {
    //Remove this object (along with it's children)
    //from current tree.
    m_pParent->RemoveChild(this);
    m_pParent = nullptr;
    return this;
  }
}

class CSpecificObject : public CObject {
public:
  virtual void foo() {
    //Specific code for this class
  }

  virtual CSpecificObject * duplicate() {
    //Overload, but the code only calls diferent constructor
    return new CSpecificObject(*this);
  }

  virtual CSpecificObject * detach() {
    //Note the code is identical.
    m_pParent->RemoveChild(this);
    m_pParent = nullptr;
    return this;
  }
}

Unfortunately the number of inherited classes increases rapidly and the duplicate code (in given example only the detach() method) is giving me a headache.

Is there a way to cleanly implement detach() methods, keeping the return type the same as the object, on which it is called?

I was thinking about CRTP, but I can not think of a way to keep the dynamic polymorphism along with compile time polymorphism:

template <Child>
class CObject {
private:
  ...
  Child * detach() {
    m_pParent->RemoveChild(this);
    m_pParent = nullptr;
    return static_cast<Child*>(this);
  }
  ...
}

//Array of CObject* pointers is no longer possible.
도움이 되었습니까?

해결책

You can add one level of abstraction:

class CObjectBase
{
    public:
        // Other methods...
        virtual CObjectBase* detach() = 0;
        virtual CObjectBase* duplicate() const = 0;
};

template <typename Child>
class CObject : public CObjectBase
{
    public:
        // ...
        Child* duplicate() const
        {
            return new Child(*static_cast<Child*>(this));
        }

        Child* detach()
        {
            m_pParent->RemoveChild(this);
            m_pParent = nullptr;
            return static_cast<Child*>(this); // Cast needed here (inherent to CRTP)
        }
        std::vector<CObjectBase*> children; // Array possible now
        // ...
};

class MyObject : public CObject<MyObject>
{
    // ...
};

In natural language: an interface for all objects (CObjectBase) have a partial implementation for its descendants (CObject<Child>), which just have to inherit this partial implementation, decreasing the amount of replicated code.

다른 팁

I was thinking about CRTP, but I can not think of a way to keep the dynamic polymorphism along with compile time polymorphism

You can mix them by providing default virtual implementations for certain interfaces using CRTP style base classes.

Thus you have the possibility to aggregate CRTP base implementations (maybe configured with additional 'policy'-template parameters) and still being able to override particular behavior in inherited classes.

Microsoft's ATL library uses this a lot. I also make use of this technique in my STTCL state machine library.

From the snippet alone it is unclear why you need detach() to return a pointer to a delivered type.

To take advantage of detach() returning a delivered type, it needs to be called using a reference to the delivered type anyway. Like this:

CSpecificObject* specific_object = new SpecificObject();
// ...
specific_object->detach()->method_declared_in_specific_object();

But this can be replaced with equivalent that works even if detach is void:

specific_object->detach();
specific_object->method_declared_in_specific_object();

If you have a reference to the base type, you can't take advantage of detach() return type:

CObject* specific_object = new SpecificObject();
//...
// !!! Won't compile:
specific_object->detach()->method_declared_in_specific_object(); 

For this reason it is unclear what are the advantages of the approach you are trying to implement.

A side not is that the duplicate() method is smelly. It breaks when delivered class does not overwrite it, but uses the default implementation from the parent class. It can be a sign that something is wrong with the high level design.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top