CreationalPatterns

The Factory Method (Slicing and Ownership Semantics)

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:

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    // 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, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, 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, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

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

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *