The Factory Method (Slicing and Ownership Semantics)

Contents[Show]

In the last installment of this blog, I introduced the Factory Method: Creational Patterns: Factory Method 1. My implementation had two serious issues: slicing and ownership semantics. Today, I fix these issues.

CreationalPatterns

To remind you, here is a simplified and slightly modified implementation of the Factory Method in my last post. First, this implementation support only the member function clone; second, also the base class Window should be cloneable.

// factoryMethodWindowIssues.cpp

#include <iostream>

// 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.

factoryMethodWindowIssues

Slicing

First: What is slicing?

  • Slicing means you want to copy an object during assignment or initialization, and you get only a part of the object.

Slicing is one of the darkest corners of C++. Let me give you a simple example:

// slice.cpp

#include <iostream>
#include <typeinfo>

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)

}

 

The expressions (1), (2), and (3) have all the same effect: The Derived part of d is removed. I assume that was not your intention.

 slice

 

You may ask yourself. Why is slicing an issue for the example factoryMethodWindowIssues.cpp. Let me quote the C++ Core Guidelines: C.67: A polymorphic class should suppress public copy/move.

Okay, there is a new question:  What is a polymorphic class?

  • A polymorphic class is a class that defines or inherits at least one virtual function.

Here is the issue. Window in the program factoryMethodWindowIssues.cpp is a polymorphic class, and it does not suppress public copy/move. A Window can be a victim of slicing. The following program exemplifies this. I just removed the reference from the function signature of the function cloneWindow:

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

 

Due to the fact that the factory function cloneWindow takes its argument by copy and not by reference anymore, slicing kicks in.

 factoryMethodWindowIssuesSlicing

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

Here is the fixed program:

// factoryMethodWindowSlicingFixed.cpp

#include <iostream>

// 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 deleted the copy constructor (line 1) and copy assignment operator (line 2). Due to the declared copy constructor, the class Window supports no move semantics. Additionally, the default constructor is also not declared. Therefore, I have to default it (line 1).

The error message of the Microsoft Visual C++ compiler comes directly to the point:

factoryMethodWindowsSlicingFixed

The necessary copy constructor is deleted.

Okay. Let me write about the ownership issue of the program.

Ownership Semantics

In general, you don't know the implementation of the factory function cloneWindow. cloneWindow returns a pointer to Window. Pointers have, by design, a flaw. They model two completely different semantics: ownership and borrowing.

  • Ownership: the caller is responsible for the Windows and must destroy it; this is the behavior that program factoryMethodWindowsIssues.cpp modeled
  • Borrowing: the callee is responsible for the Window and borrows it from the caller

Let me emphasize this one more.

  • Owner: You are the owner of the Window. You have to take care of it and destroy it. If not, you have a memory leak.
  • Borrower: You are not the owner of the Window. You can not destroy it. If you destroy it, you have a double delete.

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

The Weak Rescue

The weak rescue is based on discipline: In modern C++, we don't transfer ownership with a raw pointer.

The Strong Rescue

The strong rescue is based on the type system. When you want to transfer ownership, use a smart pointer. You have two choices:

  • std::unique_ptr<Window>: Returning a std::unique_ptr<Window> means that the caller is the owner. The std::unique_ptr<Window> behaves such as a local. When it goes out of scope, it is automatically destroyed.
  • std::shared_ptr<Window>: Returning a std::shared_ptr<Window> means that the caller and the called share ownership. When neither the caller nor the callee needs the std::shared_ptr<Window> anymore, it is automatically destroyed.

The following program factoryMethodUniquePtr.cpp uses a std::unique_ptr<Window> to explicitly transfer ownership. Additionally, a std::unique_ptr can not be copied. Consequentially, the factory function creates a new std::unique_ptr<Window>.

// factoryMethodUniquePtr.cpp

#include <iostream>
#include <memory> // Product class Window{ public: virtual std::unique_ptr<Window> create() { std::cout << "Create Window" << '\n'; return std::make_unique<Window>(); } }; // Concrete Products class DefaultWindow: public Window { std::unique_ptr<Window> create() override { std::cout << "Create DefaultWindow" << '\n'; return std::make_unique<DefaultWindow>(); } }; class FancyWindow: public Window { std::unique_ptr<Window> create() override { std::cout << "Create FancyWindow" << '\n'; return std::make_unique<FancyWindow>(); } }; // Concrete Creator or Client auto createWindow(std::unique_ptr<Window>& oldWindow) { return oldWindow->create(); } int main() { std::cout << '\n'; std::unique_ptr<Window> window = std::make_unique<Window>(); std::unique_ptr<Window> defaultWindow = std::make_unique<DefaultWindow>(); std::unique_ptr<Window> fancyWindow = std::make_unique<FancyWindow>(); const auto window1 = createWindow(window); const auto defaultWindow1 = createWindow(defaultWindow); const auto fancyWindow1 = createWindow(fancyWindow); std::cout << '\n'; }

 

Finally, here is the output of the program.

factoryMethodUniquePtr

Each create member function returns a std::unique_ptr<Window>, but creates under the hood a std::unique_ptr<Window>, a std::unique_ptr<DefaultWindow>, or a std::unique_ptr<FancyWindow>. Therefore, the polymorphic behavior of the createWindow function is preserved.

Additionally, this implementation based on std::unique_ptr solves the slicing issue because a std::unique_ptr<Window> cannot be copied.

What's Next?

The final program factoryMethodUniquePtr.cpp is fine. It overcomes the slicing and ownership issues. Let me elaborate in my next post on the most controversial pattern of the Design Patterns book: the Singleton pattern.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, and Wolfgang Fütterer.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, Ralf Abramowitsch, John Nebel, Mipko, and Alicja Kaminska.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

 

 

 

 

 

Mentoring

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 831

Yesterday 4232

Week 831

Month 11760

All 10355067

Currently are 112 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments