3
\$\begingroup\$

I have tried my hand at the abstract factory pattern in C++:

#include <iostream>

class AbstractFactory;
class AbstractDoor;
class AbstractWindow;
class AbstractDoor;

class AbstractFactory {
public:
    virtual AbstractWindow* buildWindow() = 0;
    virtual AbstractDoor* buildDoor() = 0;
};

class AbstractDoor {
public:
    virtual void identify() = 0;
};

class WoodDoor : public AbstractDoor {
public:
    void identify() override {
        std::cout << "This is a wooden door" << std::endl;
    }
};

class GumDoor: public AbstractDoor {
public:
    void identify() override {
        std::cout << "This is a gum door" << std::endl;
    }
};

class AbstractWindow {
public:
    virtual void identify() = 0;
};

class WoodWindow : public AbstractWindow {
public:
    void identify() override {
        std::cout << "This is a wooden window" << std::endl;
    }
};

class GumWindow : public AbstractWindow {
public:
    void identify() override {
        std::cout << "This is a gum window" << std::endl;
    }
};

class GumFactory : public AbstractFactory {
public:
    AbstractWindow* buildWindow() override {
        auto* window = new GumWindow();
        return window;
    }

    AbstractDoor* buildDoor() override {
        auto* door = new GumDoor();
        return door;
    }

};

class WoodFactory : public AbstractFactory {
public:
    AbstractWindow* buildWindow() override {
        return new WoodWindow();
    }

    AbstractDoor* buildDoor() override {
        return new WoodDoor();
    }

};

int main() {

    std::cout << "Let's build a wooden window!" << std::endl;
    WoodFactory woodfac = WoodFactory();
    AbstractWindow* woodwindow = woodfac.buildWindow();
    woodwindow->identify();

    std::cout << "Let's build a gum door!" << std::endl;
    GumFactory gumfac = GumFactory();
    AbstractDoor* gumdoor = gumfac.buildDoor();
    gumdoor->identify();


    return 0;
}

Questions:

  • Is the Abstract Factory Pattern correctly implemented?
  • I'm probably not using any of the newer features (C++14) I should be using as I haven't touched C++ in more than a decade. What can be done to improve the code?

I would appreciate pointers in any and all directions.

\$\endgroup\$
3
  • \$\begingroup\$ I wouldn't call C++14 "new" these days. That's two versions ago now. \$\endgroup\$ Jan 22 at 18:13
  • 2
    \$\begingroup\$ Minor: WoodFactory woodfac = WoodFactory(); makes a useless copy. Use WoodFactory woodfac(); instead, or even better WoodFactory woodfac{}; \$\endgroup\$
    – Aganju
    Jan 23 at 4:44
  • 3
    \$\begingroup\$ @Aganju, not since C++17. NRVO gets applied and copy constructor will not called (it is a guarantee regardless of optimizations). Before that, with optimizations, it was allowed to elide copy as well (not guaranteed, compiler dependent). \$\endgroup\$ Jan 23 at 9:19

1 Answer 1

5
\$\begingroup\$

Decide Which Pattern You are Implementing

Do you actually want an abstract factory type that can hold different types of factories? Are you going to need, say, a hash table that looks up factories of different types and returns a polymorphic reference to an abstract factory object?

Are these factories going to need instance data, or should they be singletons with static methods?

Or is what you want a specific type of factory that can return abstracted references to an interface? Would a templated singleton work for you just as well?

If you do want the pattern you implemented here, with polymorphic abstract factory interfaces, I’ll implement it after the simpler versions.

Use Smart Pointers, not new

Currently, your factory objects all look something like

return new WoodWindow();

These references must be manually managed.. They leak if you ever forget to free them. They’ll corrupt the heap if you free them twice. You can’t use a different allocator, for example if you need each thread in the program to allocate from its own local pool. Any class that contains a reference from the factory cannot use the default destructor, copy constructor or assignment.

In fact, as I’ll get to, the only reason your program doesn’t crash with a memory leak or corruption bug (that’d be a real pain to find) is that you never bothered to free any of your memory. More on this later.

If your class has very little per-instance data or is moveable, it’s efficient to return a temporary of the class and assign it. Here, you cannot, because you explicitly want a polymorphic pointer.

Therefore, your want to return a std::unique_ptr<AbstractWindow> or a std::unique_ptr<AbstractDoor>. This factory function is already in the STL, in >memory>.

const std::unique_ptr<AbstractWindow> upWoodWindow = std::make_unique<WoodWindow>();

Unlike an abstract factory, this could create the object using a constructor specific to that type of window, and store it in an abstract pointer, such as the hypothetical:

std::make_unique<WoodWindow>( WoodWindow::teak, WoodWindow::shutters );

Any object that contains one of these smart pointers will automatically free its memory when it’s destroyed. A class that contains only smart pointers, and not raw pointers created with new (or C-style resource allocations) can use the implicit destructor, and it will just work.

If you need some other type of pointer, you could create it by assigning from std::unique_ptr. For example, one tricky thing about a unique_ptr is that you cannot copy them—there is supposed to be one and only one in existence at any given time! So you might instead want a std::shared_ptr, which keeps a reference count and destroys the object only when there are no reference left. You can get one of those with:

const std::shared_ptr<const AbstractDoor> spGumDoor = std::make_unique<GumDoor>();

Or if you want a polymorphic object reference:

const std::unique_ptr<AbstractWindow> upWindow = std::make_unique<WoodWindow>();
auto& woodWindow = *upWindow; // Reference to AbstractWindow.
upWindow->identify();
woodWindow.identify();

In case you do in fact need the full abstract pattern, the only changes you would need to make to your code would be to change the return type from AbstractWindow* and AbstractDoor* to std::unique_ptr<AbstractWindow> and std::unique_ptr<AbstractDoor>—and to fix one serious bug. Namely:

Every Abstract Base Class Needs a virtual Destructor!

This is a big one. This particular toy program frees all its memory when it returns from main. Any real-world program that needs this pattern would leak memory or corrupt the heap in unpredictably dangerous ways whenever a factory got destroyed, because you never declare a destructor. The default implicit destructor doesn’t delete anything. And, worse, it does not call the derived class’ destructor.

In the real world, any class hierarchy complex enough that you’d really need this will need a non-trivial destructor. This would include, for example, any factory object that uses dynamic memory to manage what kind of windows and doors it creates. This will not get called unless you give your base class something like

virtual ~AbstractWindow() = default;

Then, any unique_ptr<AbstractWindow> will call the appropriate destructor for the object it stored, through the interface’s virtual function table, when it is destroyed. You don’t need to do anything special in any of the derived classes. The implicit destructors the compiler creates automatically for them should work. But any abstract base class should declare its destructor virtual. (Technically, it only absolutely must do this whenever base-class pointers are used to destroy the object, and not just as weak references, but there is practically no cost to declaring a destructor virtual if the class has another virtual function anyway.)

If you need to implement a yser-defined destructor in a derived class, be sure to declare it override so the compiler will tell you when the base class lacks a virtual destructor. You will save yourself a lot of hassle tracking down memory leaks later.

Many compilers have flags that enable warnings whenever you do not give a base class a virtual destructor, and it would be a good idea to turn those warnings on.

Use const and static Where Appropriate

Most of your functions, such as .identify() and .buildDoor(), don’t modify the objects, and should therefore be const so you can use const objects.

In fact, none of your factory methods use any per-instance data at all. If they were storing instance data, passing it to the factory methods, and possibly modifying it (for example, giving each object a unique serial number), it would make sense for them to use this interface. But, if it’s only ever returning default objects, it should be a singleton with static factory methods.

Prefer Composition to Inheritance

Your AbstractFactory is just a composition of an AbstractDoor allocator and an AbstractWindow allocator. You don’t really need to combine them into one factory class at all; you can just create (smart or dumb) pointers to AbstractWindow and AbstractDoor by assigning to them.

So let’s turn this into something that meaningfully uses the pattern by creating some kind of object that couples a window and a door:

template < typename WindowT, typename DoorT> struct HouseParts
{
  std::unique_ptr<WindowT> windowRef;
  std::unique_ptr<DoorT> doorRef;
};

template< typename WindowT, typename DoorT > class HousePartsFactory
{
  public:
    static HouseParts< WindowT, DoorT > build()
    {
      return { std::make_unique<WindowT>(), std::make_unique<DoorT>() };
    }
};

If you want to get fancier, you can restrict the template so that it checks whether your WindowT is actually a window and your DoorT is actually a door. This is a good idea here, since the two classes accidentally duck-type to each other, and the compiler would not otherwise catch a bug where you write them in reverse order. One way to do this in C++20 is with a concept:

template<class WindowT>
  concept type_of_window = std::derived_from< WindowT, AbstractWindow >;

template<class DoorT>
  concept type_of_door = std::derived_from< DoorT, AbstractDoor >;

Which then lets you write:

template < type_of_window WindowT, type_of_door DoorT >

The older methods, such as restrict or std::enable_if, will still work, too.

This is not run-time polymorphism. It does not define an interface that can reference any kind of HouseParts. Each function that uses this must be templated to some type of window and door.

In fact, because the factory method is static, we do not need to create any instance of the factory object at all. This would change based on what data it needs at runtime. If all factories in the program are supposed to generate unique serial numbers for each object they create, for example, they should increment a shared counter. If an individual factory is configurable, such as telling it to switch to making its wood windows and doors from fir instead of oak, it would then need some instance data, and its factory method could no longer be static.

How to store the objects in the struct has trade-offs. I used unique_ptr references, which are the closest equivalent to dumb pointers in modern C++. These can be moved efficiently, but cannot be automatically copied. This also facilitates, later on, allowing a generic house-parts interface to replace one with a house part of a different type.

If you want the object to be copyable, like with auto moreHouseParts = someHouseParts;, you must define your own copy constructor that makes a deep copy, or change std::unique_ptr to std::shared_ptr (which has higher overhead), or store the door and window objects themselves rather than pointers to them. But you always really had to do one of these three things, or your program would have a memory-management bug. The compiler just didn’t check for them. Everybody just assumed that any big C++ program did.

A Few Minor Things

If I don’t tell you that it’s slightly more efficient, as well as shorter, to write << "...\n"; than << "..." << std::endl;, someone else is sure to. So I'll save them the trouble.

In this simple example, many of the functions and constructors could be declared constexpr. The downside is, you might later paint yourself into a corner, where you can’t remove the constexpr from the interface without breaking your codebase, but leaving it might prevent an implementation from doing something it needs to, such as logging or allocating dynamic memory. One way around that is to declare a particular specialization of the class template to have a constexpr constructor, without putting any limitations on the more general version.

I didn’t make anything noexcept, since in general we can’t be sure what a derived class implementing our interface might need to do. You can often eke a little more optimization out of this keyword, if you know what you’re doing. A good example of what to make noexcept in a production interface would be moves and swaps.

Putting it All Together (with Templates)

#include <concepts>
#include <iostream>
#include <memory>
#include <string>

using namespace std::literals::string_literals;

class AbstractDoor {
  public:
    virtual const std::string& to_string() const = 0;
    virtual ~AbstractDoor() = default;
};

std::ostream& operator<< ( std::ostream& os, const AbstractDoor& door )
{
  return os << door.to_string();
}

class AbstractWindow {
  public:
    virtual const std::string& to_string() const = 0;
    virtual ~AbstractWindow() = default;
};

std::ostream& operator<< ( std::ostream& os, const AbstractWindow& window )
{
  return os << window.to_string();
}

class WoodDoor : public AbstractDoor {
  public:
    const std::string& to_string() const override
    {
      static const std::string message = "This is a wooden door."s;
      return message;
    }
};

class GumDoor : public AbstractDoor {
  public:
    const std::string& to_string() const override
    {
      static const std::string message = "This is a gum door."s;
      return message;
    }
};

class WoodWindow : public AbstractWindow {
  public:
    const std::string& to_string() const override
    {
      static const std::string message = "This is a wooden window."s;
      return message;
    }
};

class GumWindow : public AbstractWindow {
  public:
    const std::string& to_string() const override
    {
      static const std::string message = "This is a gum window."s;
      return message;
    }
};

template<class WindowT>
  concept is_a_window = std::derived_from< WindowT, AbstractWindow >;

template<class DoorT>
  concept is_a_door = std::derived_from< DoorT, AbstractDoor >;

template < is_a_window WindowT, is_a_door DoorT > struct HouseParts
{
  std::unique_ptr<WindowT> windowRef;
  std::unique_ptr<DoorT> DoorRef;
};

template< is_a_window WindowT, is_a_door DoorT >
  class HousePartsFactory
{
  public:
    static HouseParts< WindowT, DoorT > build()
    {
      return { std::make_unique<WindowT>(), std::make_unique<DoorT>() };
    }
};

#include <cstdlib>

int main() {
  using std::cout;
  std::cout << "Let's build a wooden window and gum door!\n";

  const auto [ windowRef, doorRef ] = HousePartsFactory< WoodWindow, GumDoor >::build();
  cout << *windowRef << ' '
       << *doorRef << '\n';

  return EXIT_SUCCESS;
}

Going through the changes:

  • There is no dynamic typing in the program. The only virtual function calls are to cout <<. Everything else is declared as a class template (and output could be a function template, if you wanted).
  • The interface is not hardcoded to print to cout, but returns a std::string that you can use for other purposes (such as logging somewhere other than standard output, or passing to a formatter).
  • There is a generic function that passes an AbstractWindow or AbstractDoor to a std::ostream.
  • All class functions that can be static or const are. This means the program never actually creates a factory object.
  • All objects in the program have their memory managed automatically.
  • As a side-effect of using std::unique_ptr instead of std::shared_ptr, the compiler will not let you copy a HouseParts object.
  • I used slightly different formatting conventions, which is a matter of personal preference.

Putting it all Together with Polymorphism

You asked about this specific pattern. First, let’s create factory objects that return an abstract data types. So let’s define an AbstractHouseParts interface, which encapsulates any kind of object representing a window and a door. This example will have a pair of getter functions.

class AbstractHouseParts {
  public:
    virtual ~AbstractHouseParts() = default;
    
    // Non-const getters:
    virtual AbstractWindow& window() = 0;
    virtual AbstractDoor& door() = 0;
    
    // const getters:
    virtual const AbstractWindow& window() const = 0;
    virtual const AbstractDoor& door() const = 0;

Now, this interface (like the implementations of AbstractDoor and AbstractWindow above) violates the Rule of Three, because it declares a user-provided destructor without a user-provided copy constructor or assignment operator. The way around this is to add some more boilerplate to the end of the class:

  protected:
    /* Declare the constructors and assignment protected, so this class
     * cannot be instantiated, except through a daughter class.
     */
    AbstractHouseParts() = default;
    AbstractHouseParts(const AbstractHouseParts&) = default;
    AbstractHouseParts(AbstractHouseParts&&) = default;
    AbstractHouseParts& operator= (const AbstractHouseParts&) = default;
    AbstractHouseParts& operator= (AbstractHouseParts&&) = default;
};

We can’t delete any of these operations or declare them private, because then daughter classes can’t implicitly access them. If you don’t care about having the compiler prevent you from creating an instance of your abstract base class, you could declare them public:. As it is, any of these that we want the child classes to implement need to be redeclared as public in the children.

We still declare HouseParts as a class template, but now each specialization of the template inherits from AbstractHouseParts. We therefore can store one in an AbstractHouseParts& reference or an AbstractHouseParts* pointer. We remove all the pointers from the HouseParts class template itself and store a pair of objects inside. if we need something moveable, we create a smart pointer to HouseParts.

template < is_a_window WindowT, is_a_door DoorT >
  class HouseParts : public AbstractHouseParts

Which contains overrides of the abstract interface and the data members

  private:
    WindowT m_window;
    DoorT m_door;

Putting it all together again, we now get:

#include <concepts>
#include <iostream>
#include <memory>
#include <string>
#include <utility>

using namespace std::literals::string_literals;

class AbstractDoor {
  public:   
    // An abstract base class needs a virtual destructor.
    virtual ~AbstractDoor() = default;
    
    virtual const std::string& to_string() const = 0;
    
  protected:
    /* We declare the default constructors and assignment protected, so
     * that this class cannot be instantiated, except through a daughter
     * class.
     */
    AbstractDoor() = default;
    AbstractDoor(const AbstractDoor&) = default;
    AbstractDoor& operator= (const AbstractDoor&) = default;
};

std::ostream& operator<< ( std::ostream& os, const AbstractDoor& door )
{
  return os << door.to_string();
}

class AbstractWindow {
  public:
    virtual ~AbstractWindow() = default;

    virtual const std::string& to_string() const = 0;

  protected:
    /* We declare the default constructors and assignment protected, so
     * that this class cannot be instantiated, except through a daughter
     * class.
     */
    AbstractWindow() = default;
    AbstractWindow(const AbstractWindow&) = default;
    AbstractWindow& operator= (const AbstractWindow&) = default;
};

std::ostream& operator<< ( std::ostream& os, const AbstractWindow& window )
{
  return os << window.to_string();
}

class WoodDoor : public AbstractDoor {
  public:
    // Default what was hidden in the base.
    WoodDoor() = default;
    WoodDoor(const WoodDoor&) = default;
    WoodDoor& operator= (const WoodDoor&) = default;
    // And by the rule of three:
    ~WoodDoor() override = default;

    const std::string& to_string() const override
    {
      static const std::string message = "This is a wooden door."s;
      return message;
    }
};

class GumDoor : public AbstractDoor {
  public:
    // Default what was hidden in the base.
    GumDoor() = default;
    GumDoor(const GumDoor&) = default;
    GumDoor& operator= (const GumDoor&) = default;
    // And by the rule of three:
    ~GumDoor() override = default;
    
    const std::string& to_string() const override
    {
      static const std::string message = "This is a gum door."s;
      return message;
    }
};

class WoodWindow : public AbstractWindow {
  public:
    // Default what was hidden in the base.
    WoodWindow() = default;
    WoodWindow(const WoodWindow&) = default;
    WoodWindow& operator= (const WoodWindow&) = default; 
    // And by the rule of three:
    ~WoodWindow() override = default;   

    const std::string& to_string() const override
    {
      static const std::string message = "This is a wooden window."s;
      return message;
    }
};

class GumWindow : public AbstractWindow {
  public:
    // Default what was hidden in the base.
    GumWindow() = default;
    GumWindow(const GumWindow&) = default;
    GumWindow& operator= (const GumWindow&) = default;
    // And by the rule of three:
    ~GumWindow() override = default;
    
    const std::string& to_string() const override
    {
      static const std::string message = "This is a gum window."s;
      return message;
    }
};

template<class WindowT>
  concept is_a_window = std::derived_from< WindowT, AbstractWindow >;

template<class DoorT>
  concept is_a_door = std::derived_from< DoorT, AbstractDoor >;
  
class AbstractHouseParts {
  public:
    virtual ~AbstractHouseParts() = default;
    
    // Non-const getters:
    virtual AbstractWindow& window() = 0;
    virtual AbstractDoor& door() = 0;
    
    // const getters:
    virtual const AbstractWindow& window() const = 0;
    virtual const AbstractDoor& door() const = 0;

  protected:
    /* Declare the constructors and assignment protected, so this class
     * cannot be instantiated, except through a daughter class.
     */
    AbstractHouseParts() = default;
    AbstractHouseParts(const AbstractHouseParts&) = default;
    AbstractHouseParts(AbstractHouseParts&&) = default;
    AbstractHouseParts& operator= (const AbstractHouseParts&) = default;
    AbstractHouseParts& operator= (AbstractHouseParts&&) = default;
};

template < is_a_window WindowT, is_a_door DoorT >
  class HouseParts : public AbstractHouseParts
{
  public:
    // By the Rule of Five:
    HouseParts(const HouseParts&) = default;
    HouseParts(HouseParts&&) = default;
    HouseParts& operator= (const HouseParts&) = default;
    HouseParts& operator= (HouseParts&&) = default;
    ~HouseParts() override = default;

    // Declare the constructor we use:
    HouseParts( const WindowT& w, const DoorT& d )
      : m_window(w), m_door(d)
    {}
    
    // This is what actually gets called:
    HouseParts( WindowT&& w, DoorT&& d )
     : m_window(std::move(w)), m_door(std::move(d))
    {}
    
    AbstractWindow& window() override
    {
      return m_window;
    }
  
    AbstractDoor& door() override
    {
      return m_door;
    }
  
    const AbstractWindow& window() const override
    {
      return m_window;
    }
  
    const AbstractDoor& door() const override
    {
      return m_door;
    }
  
  private:
    WindowT m_window;
    DoorT m_door;
};

template< is_a_window WindowT, is_a_door DoorT >
  class HousePartsFactory
{
  public:
    static HouseParts< WindowT, DoorT > build()
    {
      return{ WindowT(), DoorT() };
    }
};

#include <cstdlib>

int main() {
  using std::cout;
  std::cout << "Let's build a wooden window and gum door!\n";

  const auto parts =
    HousePartsFactory< WoodWindow, GumDoor >::build();
  const AbstractHouseParts& abstract = parts;
  cout << abstract.window() << ' '
       << abstract.door() << '\n';

  return EXIT_SUCCESS;
}

This has no function to explicitly create a generic object, because the abstract interface is the superclass of all the objects the factory creates, and therefore you can just assign their return values to an abstract reference or pointer.

This implementation has no ability to set its data members after the class is created, but you could also turn a class with a unique_ptr<AbstractWindow> and a unique_ptr<AbstractDoor> into a kind of generic placeholder. It could set these two generic smart pointers to reference any type of window and any type of door, so you could write generic setters for it.

Hiding the default constructors, to make the compiler stop us from accidentally instantiating an abstract base class, required a significant amount of boilerplate. So, decide if that’s worth it.

Putting it All Together One Last Time

What you originally had, though, was not a static factory that creates abstract objects (or objects that implicitly convert to an abstract base class), but an abstract factory type with polymorphic subclasses representing different types of factory. Maybe you want a data structure representing arbitrary factory classes at runtime.

This can still be done. Since the abstract interface does not know at compile time what type a factory will create, the factory member function must return a smart pointer to the abstract datatype, which will be automatically destroyed when its lifetime expires.

The AbstractHousePartsFactory abstract base class therefore has one function in its interface, plus boilerplate.

virtual std::unique_ptr<AbstractHouseParts> build() = 0;

The HousePartsFactory class template now inherits from it:

template< is_a_window WindowT, is_a_door DoorT >
  class HousePartsFactory : public AbstractHousePartsFactory

Its implementation of .build() essentially wraps the code I wrote above, to implicitly convert a smart pointer of a daughter class to a smart pointer of the parent class.

std::unique_ptr<AbstractHouseParts> build() override
{
  return std::make_unique<HouseParts< WindowT, DoorT >>();
}

It turns out I'd protected the default constructor of HouseParts, though, so I needed to go back to the HouseParts class definition and re-declare HouseParts() = default as public for this to work.

Finally, I made the statically-typed factory member from before available as a static constexpr member function, renamed buildStatic(). This lets you still use it when you don’t need polymorphism.

The full, compilable code: This has a test driver that stores different types of factories in a data structure, then the polymorphic output of each factory in another, with automatic memory management.

#include <concepts>
#include <iostream>
#include <memory>
#include <string>
#include <utility>

using namespace std::literals::string_literals;

class AbstractDoor {
  public:   
    // An abstract base class needs a virtual destructor.
    virtual ~AbstractDoor() = default;
    
    virtual const std::string& to_string() const = 0;
    
  protected:
    /* We declare the default constructors and assignment protected, so
     * that this class cannot be instantiated, except through a daughter
     * class.
     */
    AbstractDoor() = default;
    AbstractDoor(const AbstractDoor&) = default;
    AbstractDoor& operator= (const AbstractDoor&) = default;
};

std::ostream& operator<< ( std::ostream& os, const AbstractDoor& door )
{
  return os << door.to_string();
}

class AbstractWindow {
  public:
    virtual ~AbstractWindow() = default;

    virtual const std::string& to_string() const = 0;

  protected:
    /* We declare the default constructors and assignment protected, so
     * that this class cannot be instantiated, except through a daughter
     * class.
     */
    AbstractWindow() = default;
    AbstractWindow(const AbstractWindow&) = default;
    AbstractWindow& operator= (const AbstractWindow&) = default;
};

std::ostream& operator<< ( std::ostream& os, const AbstractWindow& window )
{
  return os << window.to_string();
}

class WoodDoor : public AbstractDoor {
  public:
    // Default what was hidden in the base.
    WoodDoor() = default;
    WoodDoor(const WoodDoor&) = default;
    WoodDoor& operator= (const WoodDoor&) = default;
    // And by the rule of three:
    ~WoodDoor() override = default;

    const std::string& to_string() const override
    {
      static const std::string message = "This is a wooden door."s;
      return message;
    }
};

class GumDoor : public AbstractDoor {
  public:
    // Default what was hidden in the base.
    GumDoor() = default;
    GumDoor(const GumDoor&) = default;
    GumDoor& operator= (const GumDoor&) = default;
    // And by the rule of three:
    ~GumDoor() override = default;
    
    const std::string& to_string() const override
    {
      static const std::string message = "This is a gum door."s;
      return message;
    }
};

class WoodWindow : public AbstractWindow {
  public:
    // Default what was hidden in the base.
    WoodWindow() = default;
    WoodWindow(const WoodWindow&) = default;
    WoodWindow& operator= (const WoodWindow&) = default; 
    // And by the rule of three:
    ~WoodWindow() override = default;   

    const std::string& to_string() const override
    {
      static const std::string message = "This is a wooden window."s;
      return message;
    }
};

class GumWindow : public AbstractWindow {
  public:
    // Default what was hidden in the base.
    GumWindow() = default;
    GumWindow(const GumWindow&) = default;
    GumWindow& operator= (const GumWindow&) = default;
    // And by the rule of three:
    ~GumWindow() override = default;
    
    const std::string& to_string() const override
    {
      static const std::string message = "This is a gum window."s;
      return message;
    }
};

template<class WindowT>
  concept is_a_window = std::derived_from< WindowT, AbstractWindow >;

template<class DoorT>
  concept is_a_door = std::derived_from< DoorT, AbstractDoor >;
  
class AbstractHouseParts {
  public:
    virtual ~AbstractHouseParts() = default;
    
    // Non-const getters:
    virtual AbstractWindow& window() = 0;
    virtual AbstractDoor& door() = 0;
    
    // const getters:
    virtual const AbstractWindow& window() const = 0;
    virtual const AbstractDoor& door() const = 0;

  protected:
    /* Declare the constructors and assignment protected, so this class
     * cannot be instantiated, except through a daughter class.
     */
    AbstractHouseParts() = default;
    AbstractHouseParts(const AbstractHouseParts&) = default;
    AbstractHouseParts(AbstractHouseParts&&) = default;
    AbstractHouseParts& operator= (const AbstractHouseParts&) = default;
    AbstractHouseParts& operator= (AbstractHouseParts&&) = default;
};

template < is_a_window WindowT, is_a_door DoorT >
  class HouseParts : public AbstractHouseParts
{
  public:
    // By the Rule of Five:
    HouseParts(const HouseParts&) = default;
    HouseParts(HouseParts&&) = default;
    HouseParts& operator= (const HouseParts&) = default;
    HouseParts& operator= (HouseParts&&) = default;
    ~HouseParts() override = default;

    // Declare the constructor we use:
    HouseParts() = default;
    
    HouseParts( const WindowT& w, const DoorT& d )
      : m_window(w), m_door(d)
    {}
    
    HouseParts( WindowT&& w, DoorT&& d )
     : m_window(std::move(w)), m_door(std::move(d))
    {}
    
    AbstractWindow& window() override
    {
      return m_window;
    }
  
    AbstractDoor& door() override
    {
      return m_door;
    }
  
    const AbstractWindow& window() const override
    {
      return m_window;
    }
  
    const AbstractDoor& door() const override
    {
      return m_door;
    }
  
  private:
    WindowT m_window;
    DoorT m_door;
};

class AbstractHousePartsFactory
{
  public:
    virtual ~AbstractHousePartsFactory() = default;
 
    // Not const, because some implementations might want to modify it.
    virtual std::unique_ptr<AbstractHouseParts> build() = 0;
    
  protected:
  /* As before, only a daughter class can be created.
   */
    AbstractHousePartsFactory() = default;
    AbstractHousePartsFactory(const AbstractHousePartsFactory&) = default;
    AbstractHousePartsFactory&
      operator= (const AbstractHousePartsFactory&) = default;
};

template< is_a_window WindowT, is_a_door DoorT >
  class HousePartsFactory : public AbstractHousePartsFactory
{
  public:
    // Allow this implementation to be created.
    HousePartsFactory() = default;
    // By the Rule of Three:
    HousePartsFactory(const HousePartsFactory&) = default;
    HousePartsFactory& operator= (const HousePartsFactory&) = default;
    ~HousePartsFactory() override = default;
  
    std::unique_ptr<AbstractHouseParts> build() override
    {
      return std::make_unique<HouseParts< WindowT, DoorT >>();
    }
  
    static constexpr HouseParts< WindowT, DoorT > buildStatic()
    {
      return{ WindowT(), DoorT() };
    }
};

#include <cstdlib>

int main() {
  using std::cout;
  // A data structure holding arbitrary types of factory:
  const std::unique_ptr<AbstractHousePartsFactory> factories[] = {
    std::make_unique<HousePartsFactory<WoodWindow, GumDoor>>(),
    std::make_unique<HousePartsFactory<GumWindow, WoodDoor>>()
  };
 
  const std::unique_ptr<const AbstractHouseParts> outputs[] = {
    factories[0]->build(),
    factories[1]->build()
  };
  
  cout << outputs[0]->window() << ' ' << outputs[0]->door() << '\n'
       << outputs[1]->window() << ' ' << outputs[1]->door() << '\n';

  return EXIT_SUCCESS;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.