C++ Composition with abstract class

2020-07-25 07:18发布

问题:

Lets say I have an abstract class that is expensive to create and copy:

class AbstractBase {
public:
    AbstractBase() {
        for (int i = 0; i < 50000000; ++i) {
            values.push_back(i);
        }
    }

    virtual void doThing() = 0;

private:
    vector<int> values;
};

It has two subclasses FirstDerived:

class FirstDerived : public AbstractBase {
public:
    void doThing() {
        std::cout << "I did the thing in FirstDerived!\n";
    }

};

and SecondDerived:

class SecondDerived : public AbstractBase {
public:
    void doThing() {
        std::cout << "I did the thing in SecondDerived!\n";
    }
};

Further, I would like to make a class that utilizes FirstDerived or SecondDerived using composition (not aggregation). Meaning that I want ComposedOfAbstractBase to own whichever temporary is passed in. If I weren't using abstract classes in this class would look like: (in C++11)

class ComposedOfWhicheverDerived {
public:
    ComposedOfWhicheverDerived(AbstractBase abstract_base) : abstract_base(std::move(abstract_base)) {;}
private:
    AbstractBase abstract_base;
};

However, this does not work with abstract classes because I cannot ever create an instance of AbstractBase, even if I am careful about not passing in a temporary AbstractBase, like so:

ComposedOfWhicheverDerived a(FirstDerived());

To the compiler this is just as bad as:

ComposedOfWhicheverDerived b(AbstractBase());

Because I still have an instance of AbstractBase in the class declaration.

The next solution I came up with is:

class ComposedOfAbstractBase {
public:
    ComposedOfAbstractBase(AbstractBase&& abstract_base) : some_derived_instance(abstract_base) {;}

private:
    AbstractBase& some_derived_instance;
};

This works perfectly (even though I don't fully understand it)! Both of these instances are valid and work as intended:

ComposedOfAbstractBase a(FirstDerived());
ComposedOfAbstractBase b(SecondDerived());

It doesn't create a copy of whatever AbstractBase temporary is passed in, and storing a reference to an AbstractBase is allowed. Though at best the reference to an rvalue reference seems unclear: it does not convey that ComposedOfAbstractBase owns whichever temporary is passed in. In addition to that, it turns out that this solution seems to be sub-optimal. To show this I created this class:

class ComposedOfFirstDerived {
public:
    ComposedOfFirstDerived(FirstDerived first_derived) : first_derived(std::move(first_derived)) {;}

private:
    FirstDerived first_derived;
};

Which can only take in a FirstDerived, so we can apply the std::move to offload ownership of the temporary. I can make an instance like so:

ComposedOfFirstDerived c(FirstDerived()); 

Interestingly enough, this class consistently is 10% faster to create than ComposedOfAbstractClass.

Does anybody know what is going on here? Why is ComposedOfFirstDerived so much faster to create than ComposedOfAbstractBase? Is there a better way to do composition with abstract classes or am I stuck with a sub-optimal solution?

Sorry if this was a mouthful of a question. I appreciate anyone who takes the time to read through it and give a genuine answer, because I am beyond stumped!

回答1:

ComposedOfAbstractBase is not a solution. You're holding a dangling reference.

Since AbstractBase is, as the name suggests, abstract - you cannot hold one by value. You can only hold one by reference or by pointer. Since a reference cannot own the object, that leaves you with the pointer. And the modern way of owning a pointer is to use unique_ptr:

class ComposedOfAbstractBasePtr {
public:
    ComposedOfAbstractBasePtr(std::unique_ptr<AbstractBase> p)
    : some_derived_instance(std::move(p))
    { }

private:
    std::unique_ptr<AbstractBase> some_derived_instance;
};

Note that your AbstractBase does not have a virtual destructor. You should fix that.