lundi 3 avril 2023

The Factory Method Design Pattern and Modern C++ - should factory functions return smart pointers, or values? [closed]

I'm catching up on "modern" C++ and a lot of what I've read says that I should prioritise value types, as they often lead to simpler code. I'm exploring how this works with a simple interpretation of the Factory Method design pattern, using a free function to avoid client code having to specify the concrete type explicitly.

I have a polymorphic type implemented by public inheritance, and I wish to store an instance of this type in another object. Thus I must store a pointer to get polymorphic behaviour on this type. This already makes it difficult to deal only with values, but I can hide this inside the Contains class if I implement clone().

class Base {
public:
    virtual std::unique_ptr<Base> clone() = 0;
    virtual void foo() = 0;
};

class Derived : public Base {
public:
    std::unique_ptr<Base> clone() override {
        return std::make_unique<Derived>(*this);
    }
    void foo() override { /* do something polymorphic */ }
};

class Contains {
public:
    void store(Base & base) {
        s_ = base.clone();
    }

    void do_foo() {
        s_->foo();  // call polymorphic function
    }
private:
    std::unique_ptr<Base> s_;
};

// Factory function
Derived derived() { return Derived {}; }

https://godbolt.org/z/P1Eddcbsj

In the above code, Contains::store() makes a copy of the value passed by reference, and takes ownership via a unique_ptr. In this case, the factory function creates a value, and copies are made (and owned) by instances of Contains. The user of the factory function does not need to worry about ownership (unique or shared?) or passing unique_ptrs to Derived around.

But there's another way to do it, where the factory function returns a unique_ptr<Base> instead of a value:

class Base { ... } // as above
class Derived { ... } // as above

class Contains {
public:
    void store(std::unique_ptr<Base> & base) {
        s_ = base->clone();
    }

    // ...

private:
    std::unique_ptr<Base> s_;
};

std::unique_ptr<Base> derived() { return std::make_unique<Derived>(); }

https://godbolt.org/z/hj4ed4v31

The store function has to take a reference otherwise the caller is unable to pass the pointer by value (because passing a smart pointer by value doesn't activate polymorphism):

int main() {
    auto d = derived();
    Contains c {};
    c.store(d);
    c.do_foo();
}

I note that the factory function should return unique_ptr and not shared_ptr, because ownership can always be "upgraded" to a shared_ptr, but it can't go in the reverse direction, so the unique_ptr is the more general type of smart pointer to return.


Questions:

In this situation, heap allocation of the polymorphic object is generally unavoidable, as the handle to the object must be a pointer (or reference) to activate polymorphic behaviour. However, this can be encapsulated, thus hidden from the client code. Or the smart pointer can be used to pass around the created object.

Given this, which of these two approaches to the simplified Factory Method pattern is the most idiomatic in modern C++, and will lead to the least problems down the track as the code grows?

Is there another approach I haven't considered that is superior, or worthy of serious consideration?

Aucun commentaire:

Enregistrer un commentaire