Software development: design pattern factory method without problems

Patterns are an important abstraction in modern software development. They offer well-defined terminology, clean documentation, and learning from the best. The classic book “Design Patterns: Elements of Reusable Object-Oriented Software” (Design Patterns for short) contains 23 patterns, including the factory method, which is one of the creation patterns.

In the last section of this blog, I introduced the factory method: “Software Development: The Design Pattern Factory Method for Creating Objects”. My implementation had two major problems: slicing and ownership semantics. Today I will solve these problems.



As a reminder, here is a simplified and slightly modified implementation of the factory method from my last article. First, this implementation only supports the member function clone; second must include the base class Window be cloneable.

// factoryMethodWindowIssues.cpp

#include 

// Product
class Window{ 
 public:                
    virtual Window* clone() { 
        std::cout << "Clone Window" << '\n';
        return new Window(*this);
    }                       
    virtual ~Window() {};
};

// Concrete Products 
class DefaultWindow: public Window { 
     DefaultWindow* clone() override { 
        std::cout << "Clone DefaultWindow" << '\n';
        return new DefaultWindow(*this);
    } 
};

class FancyWindow: public Window { 
    FancyWindow* clone() override { 
        std::cout << "Clone FancyWindow" << '\n';
        return new FancyWindow(*this);
    } 
};

// Concrete Creator or Client                             
Window* cloneWindow(Window& oldWindow) {                    
    return oldWindow.clone();
}
  
int main() {

    std::cout << '\n';

    Window window;
    DefaultWindow defaultWindow;
    FancyWindow fancyWindow;

    const Window* window1 = cloneWindow(window);
    const Window* defaultWindow1 = cloneWindow(defaultWindow);
    const Window* fancyWindow1 = cloneWindow(fancyWindow);
  
    delete window1;
    delete defaultWindow1;
    delete fancyWindow1;

    std::cout << '\n';
  
}

The program produces the expected polymorphic behavior.



First things first: what is carving?

  • carving means that you will copy an object during allocation or initialization and get only part of the object.

Slicing is one of the darkest corners of C++, as this simple example illustrates:

// slice.cpp

#include 
#include 

struct Base { };
 
struct Derived : Base { };

void displayTypeinfo(const Base& b) {
    std::cout << typeid(b).name() << '\n'; 
}

void needB(Base b) {
    displayTypeinfo(b);
};
 
int main() {

    Derived d;
  
    Base b = d;          
    displayTypeinfo(b);      // (1)
  
    Base b2(d);        
    displayTypeinfo(b2);     // (2)
  
    needB(d);                // (3)

}

Expressions (1), (2) and (3) all have the same effect: The derivative part of d will be removed. But it's probably not on purpose.



Why is clipping a problem for the example factoryMethodWindowIssues.cpp? Let me quote the C++ Core Guidelines: C.67: A polymorphic class should suppress public copy/move. This brings up the next question: What is a polymorphic class?

  • One polymorphic class is a class that defines or inherits one or more virtual functions.

Here's the problem: Window in the program factoryMethodWindowIssues.cpp is a polymorphic class that does not suppress public copy/move. ONE Window can fall victim to slicing, as the following code snippet illustrates. I only have the reference from the function's function signature cloneWindow away:

// Concrete Creator or Client                             
Window* cloneWindow(Window oldWindow) {                    
    return oldWindow.clone();
}

Because the factory function cloneWindow takes his argument as a copy and no longer as a reference, clipping comes into play.



Let's see what happens when I use the rule: C.67: A polymorphic class should suppress public copy/move. obey.

Here is the corrected program:

// factoryMethodWindowSlicingFixed.cpp

#include 

// Product
class Window{ 
 public:      
    Window() = default;                          // (3)
    Window(const Window&) = delete;              // (1)
    Window& operator = (const Window&) = delete; // (2)
    virtual Window* clone() { 
        std::cout << "Clone Window" << '\n';
        return new Window(*this);
    }                       
    virtual ~Window() {};
};

// Concrete Products 
class DefaultWindow: public Window { 
     DefaultWindow* clone() override { 
        std::cout << "Clone DefaultWindow" << '\n';
        return new DefaultWindow(*this);
    } 
};

class FancyWindow: public Window { 
    FancyWindow* clone() override { 
        std::cout << "Clone FancyWindow" << '\n';
        return new FancyWindow(*this);
    } 
};

// Concrete Creator or Client                             
Window* cloneWindow(Window oldWindow) {           // (4)           
    return oldWindow.clone();
}
  
int main() {

    std::cout << '\n';

    Window window;
    DefaultWindow defaultWindow;
    FancyWindow fancyWindow;

    const Window* window1 = cloneWindow(window);
    const Window* defaultWindow1 = cloneWindow(defaultWindow);
    const Window* fancyWindow1 = cloneWindow(fancyWindow);
  
    delete window1;
    delete defaultWindow1;
    delete fancyWindow1;

    std::cout << '\n';
  
}

I have the copy constructor (1) and copy assignment operator (2) on delete set. Because of the declared copy constructor, the class supports Window no movement semantics. Also, the compiler does not create the default constructor. Therefore, I have to request it (1).

The Microsoft Visual C++ compiler error message sums it up:



The required copy constructor is not available.

In general, the implementation of the factory function is known cloneWindow Does not. cloneWindow returns a pointer Window Return. Pointers have an implicit bug. They model two completely different semantics: ownership and lending.

  • possession: The ringer is for it Window responsible and must destroy it. The behavior models the program factoryMethodWindowsIssues.cpp.
  • loan: The caller is responsible for the window and lends it to the caller.

Let me stress this again:

  • Owner: You are the owner of the window, you must take care of it and destroy it. Otherwise there will be a memory leak.
  • borrower: You are not the owner of the window and must not destroy it. Otherwise, it is deleted twice.

How can we overcome this design flaw? The rescue consists of two components. A weak one based on discipline and a strong one based on the type system.

Weak saving is based on discipline: in modern C++ we don't transfer ownership with a raw pointer.

The strong save is based on the type system. If you want to transfer ownership, you need a smart pointer. There are two ways to do this:

  • std::unique_ptr: The return of a std::unique_ptr means the caller is the owner. To std::unique_ptr behaves like a local variable. If it goes out of scope, it is automatically destroyed.
  • std::shared_ptr: Returns one std::shared_ptr means that the caller and the called share ownership. If neither the caller nor the called std::shared_ptr more necessary, it will be automatically destroyed.

The following program factoryMethodUniquePtr.cpp uses one std::unique_ptrexplicitly transferring ownership. Additionally, a std::unique_ptr cannot be copied. As a result, the factory function creates a new one std::unique_ptr.

// factoryMethodUniquePtr.cpp

#include 
#include 

// Product
class Window{ 
 public: 
    virtual std::unique_ptr create() { 
        std::cout << "Create Window" << '\n';
        return std::make_unique();
    } 
};

// Concrete Products 
class DefaultWindow: public Window {
    std::unique_ptr create() override { 
        std::cout << "Create DefaultWindow" << '\n';
        return std::make_unique();
    } 
};

class FancyWindow: public Window {
    std::unique_ptr create() override { 
        std::cout << "Create FancyWindow" << '\n';
        return std::make_unique();
    } 
};

// Concrete Creator or Client
auto createWindow(std::unique_ptr& oldWindow) { 
    return oldWindow->create();
}
  
int main() {

    std::cout << '\n';

    std::unique_ptr window = std::make_unique();
    std::unique_ptr defaultWindow = std::make_unique();
    std::unique_ptr fancyWindow = std::make_unique();
  
    const auto window1 = createWindow(window);
    const auto defaultWindow1 = createWindow(defaultWindow);
    const auto fancyWindow1 = createWindow(fancyWindow);

    std::cout << '\n';
  
}

Finally, here is the output from the program.



Every create-The membership function starts std::unique_ptr back, but generates one under the hood std::unique_ptra std::unique_ptr or a std::unique_ptr. Therefore, the polymorphic behavior remains createWindow-Get function.

Furthermore, this dissolves std::unique_ptr based implementation solves the slicing problem automatically since a std::unique_ptr cannot be copied.

The final program factoryMethodUniquePtr.cpp is correct. It overcomes the issues of slicing and ownership semantics. In my next article, I will take a closer look at the most controversial pattern in the Design Patterns book: the Singleton.


()

To the home page

Leave a Comment