开发者

C++ Design pattern for using a abstract class object in another class

开发者 https://www.devze.com 2023-03-13 00:54 出处:网络
I\'m having a class with 2 pure virtual methods and another class which needs to use an object of this class. I want to allow the user of this class to specify which derivation of the abstract class s

I'm having a class with 2 pure virtual methods and another class which needs to use an object of this class. I want to allow the user of this class to specify which derivation of the abstract class should be used inside of it. I'm struggling to figure out what the right way is.

struct abstract {
    virtual int fst_func() = 0;
    virtual void sec_func(int) = 0;
};

// store an instance of "abstract".
class user_of_abstract
{
private:
    abstract* m_abstract;

public:
    // Pass a pointer to an "abstract" object. The caller takes care of the memory resource.
    user_of_abstract_base(abstract* a) : m_abstract(a) { }

    // Pase any type, which needs to be derived from "abstract" and create a copy. Free memory in destructor.
    template<class abstract_type>
    user_of_abstract_base(abstract_type const& a) : m_abstract(new abstract_type(a)) { }

    // use the stored member to call fst_func.
    int use_fst_func() {
        return this->m_abstract->fst_func();
    }

    // use the stored member to call sec_func.
    void use_sec_func(int x) {
        this->m_abstract->sec_func(x);
    }
};

// use boost::shared_ptr
class user_of_abstract
{
private:
    boost::shared_ptr<abstract> m_abstract;

public:
    // Pass a pointer to an "abstract" object. The caller takes care of the memory resource.
    user_of_abstract_base(boost::shared_ptr<abstract> a) : m_abstract(a) { }

    // use the stored member to call fst_func.
    int use_fst_func() {
        ret开发者_StackOverflow社区urn this->m_abstract->fst_func();
    }

    // use the stored member to call sec_func.
    void use_sec_func(int x) {
        this->m_abstract->sec_func(x);
    }
};

// pass a pointer of an "abstract" object wherever needed.
struct user_of_abstract
{
    // use the passed pointer to an "abstract" object to call fst_func.
    int use_fst_func(abstract* a) {
        return a->fst_func();
    }

    // use the passed pointer to an "abstract" object to call sec_func.
    void use_sec_func(abstract* a, int x) {
        a->sec_func(x);
    }
};

It's important to note that parameter "x" from sec_func() needs to be a value returned by fst_func() on the same "abstract" instance.

EDIT: Added another approach using boost::shared_ptr which should take the most advantages.


I would say that passing the abstract object into the constructor of your user is the proper approach as the methods of the user depend being called on the same abstract object. I would even go further and make the x parameter an internal state of your user as you have said it's important that this value is the one returned from a call from the first function.

Update: If you are worried about the lifetimes then you could make use of the various smart pointer options available in for example boost. Those should cover most usage scenarios.


Since you say the second function should use the output of the first. I guess first approach will decrease chance of mistakes. You can even modify it to the following:

int use_fst_func() {
    return x=this->m_abstract->fst_func();
}

void use_sec_func() {
    this->m_abstract->sec_func(x);
}

protected:
   int x;


You're putting yourself in a sea of maintenance trouble.

In your first example...

there's really no need for the template constructor. It's speced as

// Parse any type, which needs to be derived from "abstract" and create a copy.

The user can already do that by creating the instance himself and pass it to the first constructor.

Also, with this:

// Free memory in destructor.

You explicitly say that you have no idea how this class should be used. As your first example is written, you need to decide: use an instance created from the outside or use an instance created on the inside. It's confusing to see an interface with one ctor taking a pointer and another ctor taking a reference, both essentially to the same type.

In my eyes, the only acceptable way of using an instance created from the outside that will not be memory-managed or an instance created from the inside that will be memory-managed, is when there's a default ctor that can initialize the internal pointer to a sensible value (but that doesn't seem to be the case here, since you want to copy another instance):

template <typename T>
class user_of_abstract
{
    bool m_owner_;
    abstract* m_abstract;

public:

    user_of_abstract_base(abstract* a = NULL)
    : m_owner(a == NULL)
    , m_abstract(m_owner ? new T(): a)
    {
    }

    ~user_of_abstract_base()
    {
        if (m_owner)
        {
            delete m_abstract;
        }
    }
}

Your second example...

is superior to the first, since you don't explicitly mix memory management with memory reference. You let shared_ptr do it implicitly. Very good, that's what it's for.

However, since you have a requirement that use_sec_func must take the output of use_fst_func as input, you stay a long way from the safe shore of the sea of maintenance problems.

For instance, what happens if use_fst_func on an instance throws an exception and use_sec_func is later called on that same instance?

How do you expect that the important information "Always call A before B. And only once. And pass the A result to B." should propagate to users of the class 2 years from now?

Why can't use_sec_func just call use_fst_func?

As for your third example...

can you give 1 single scenario when you'd want to use this instead of just calling the instance functions directly?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号