ClassIdioms

Covariant Return Type

The Covariant Return Type of a member function allows an overriding member function to return a narrower type. This is particularly useful when you implement the Prototype Pattern.

 

ClassIdioms

I used the Covariant Return Type in my previous posts without explaining it. Let’s change this.

Covariant Return Type

Let me start with the naive implementation.

The Naive Version

The following program covariantReturnType.cpp applies the Prototype Pattern.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    // covariantReturnType.cpp
    
    #include <iostream>
    #include <string>
    
    class Window{                                  // (1)
     public:                
        virtual Window* clone() { 
            return new Window(*this);
        }
        virtual std::string getName() const {
            return "Window";
        }                       
        virtual ~Window() {};
    };
    
    class DefaultWindow: public Window {          // (2)
         DefaultWindow* clone() override { 
            return new DefaultWindow(*this);
        } 
        std::string getName() const override {
            return "DefaultWindow";
        }   
    };
    
    class FancyWindow: public Window {           // (3)
        FancyWindow* clone() override { 
            return new FancyWindow(*this);
        } 
        std::string getName() const override {
            return "FancyWindow";
        }   
    };
                    
    Window* cloneWindow(Window& oldWindow) {    // (4)                
        return oldWindow.clone();
    }
      
    int main() {
    
        std::cout << '\n';
    
        Window window;
        DefaultWindow defaultWindow;
        FancyWindow fancyWindow;
    
        const Window* window1 = cloneWindow(window);
        std::cout << "window1->getName(): " << window1->getName() << '\n';
    
        const Window* defaultWindow1 = cloneWindow(defaultWindow);
        std::cout << "defaultWindow1->getName(): " << defaultWindow1->getName() << '\n';
    
        const Window* fancyWindow1 = cloneWindow(fancyWindow);
        std::cout << "fancywindow1->getName(): " << fancyWindow1->getName() << '\n';
      
        delete window1;
        delete defaultWindow1;
        delete fancyWindow1;
    
        std::cout << '\n';
      
    }
    

     

    The interface-class Window (line 1) has a virtual clone function. The clone function returns a copy of itself. The derived classes such as DefaultWindow (line 2) and FancyWindow (line 3) also return a copy of itself. The function cloneWindow (line 4) uses the virtual member function and creates clones of the incoming windows. Additionally, I implemented a virtual getName function to visualize the virtual dispatch.

    The output of the program is as expected.

    covariantReturnType

    Did you happen to notice the use of the Covariant Return Type in this example? Window‘s virtual clone member function returns a Window pointer, but DefaultWindow‘s virtual clone member function a DefaultWindow pointer, and FancyWindow*s virtual clone member function a FancyWindow pointer. This means the return type is covariant: If a derived class member function returns a more-derived type than its overridden base class member function, the derived class return type is called covariant.

    Additionally, there is a small observation I want to share. Although DeaultWindow‘s and FancyWindows‘s virtual member functions clone are private, the function cloneWindow (line 4) can invoke them. The reason is simple: The member function cloneWindow uses the public interface of the interface-class Window.

    I called this implementation naive. Why?

    Ownership Semantics

    In general, you don’t know the implementation of the clone function. clone returns a pointer to a 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 them; this is the behavior that the program covariantReturnType.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.

    With C++11, it is easy to overcome this pointer flaw. Use a std::unique_ptr<Window> or a std::shared_ptr<Window>. 

    std::unique_ptr

    Returning  std::unique_ptr<Window> means that the caller is the owner. The std::unique_ptr<Window> behaves as a local. When it goes out of scope, it is automatically destroyed. Additionally, you can simulate the Covariant Return Type.

    Thanks to the remark from Alf P. Steinbach on the facebook group C++ Enthusiasts, I want to clarify. Both examples using the smart pointers std::unique_ptr and std::shared_ptr do not use a covariant return type. The smart pointers have different dynamic types. This is, in this use-case, sufficient to simulate the behavior of the first program covariantReturntype.cpp

    // covariantReturnTypeUniquePtr.cpp
    
    #include <iostream>
    #include <memory>
    #include <string>
    
    class Window{ 
     public:                
        virtual std::unique_ptr<Window> clone() { 
            return std::make_unique<Window>(*this);          // (1)
        }
        virtual std::string getName() const {
            return "Window";
        }                       
        virtual ~Window() {};
    };
    
    class DefaultWindow: public Window { 
         std::unique_ptr<Window> clone() override { 
            return std::make_unique<DefaultWindow>(*this);  // (2)
        } 
        std::string getName() const override {
            return "DefaultWindow";
        }   
    };
    
    class FancyWindow: public Window { 
        std::unique_ptr<Window> clone() override { 
            return std::make_unique<FancyWindow>(*this);   // (3)
        } 
        std::string getName() const override {
            return "FancyWindow";
        }   
    };
                    
    auto cloneWindow(std::unique_ptr<Window>& oldWindow) {                    
        return oldWindow->clone();
    }
      
    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 = cloneWindow(window);
        std::cout << "window1->getName(): " << window1->getName() << '\n';
    
        const auto defaultWindow1 = cloneWindow(defaultWindow);
        std::cout << "defaultWindow1->getName(): " << defaultWindow1->getName() << '\n';
    
        const auto fancyWindow1 = cloneWindow(fancyWindow);
        std::cout << "fancyWindow1->getName(): " << fancyWindow1->getName() << '\n';
    
        std::cout << '\n';
      
    }
    

     

    The virtual clone member function’s return type is a std::unique_ptr<Window>. Additionally,  the returned object is a std::make_unique<Window>(*this) (line 1), a std::make_unique<DefaultWindow>(*this) (line 2), or a std::make_unique<FancyWindow>(*this) (line 3), respectively.

    The output of the program is identical to the previous one.

    covariantReturnTypeUniquePtr

     
    For completeness, let me implement this example using a std::shared_ptr.

    std::shared_ptr

    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.

    // covariantReturnTypeSharedPtr.cpp
    
    #include <iostream>
    #include <memory>
    #include <string>
    
    class Window{ 
     public:                
        virtual std::shared_ptr<Window> clone() { 
            return std::make_shared<Window>(*this);
        }
        virtual std::string getName() const {
            return "Window";
        }                       
        virtual ~Window() {};
    };
    
    class DefaultWindow: public Window { 
         std::shared_ptr<Window> clone() override { 
            return std::make_shared<DefaultWindow>(*this);
        } 
        std::string getName() const override {
            return "DefaultWindow";
        }   
    };
    
    class FancyWindow: public Window { 
        std::shared_ptr<Window> clone() override { 
            return std::make_shared<FancyWindow>(*this);
        } 
        std::string getName() const override {
            return "FancyWindow";
        }   
    };
                    
    auto cloneWindow(std::shared_ptr<Window>& oldWindow) {                    
        return oldWindow->clone();
    }
      
    int main() {
    
        std::cout << '\n';
    
        std::shared_ptr<Window> window = std::make_shared<Window>();
        std::shared_ptr<Window> defaultWindow = std::make_shared<DefaultWindow>();
        std::shared_ptr<Window> fancyWindow = std::make_shared<FancyWindow>();
    
        const auto window1 = cloneWindow(window);
        std::cout << "window1->getName(): " << window1->getName() << '\n';
    
        const auto defaultWindow1 = cloneWindow(defaultWindow);
        std::cout << "defaultWindow1->getName(): " << defaultWindow1->getName() << '\n';
    
        const auto fancyWindow1 = cloneWindow(fancyWindow);
        std::cout << "fancyWindow1->getName(): " << fancyWindow1->getName() << '\n';
    
        std::cout << '\n';
      
    }
    

     

    Porting the example covariantReturnTypeUniquePtr.cpp into covariantReturnTypeSharedPtr.cpp is a piece of cake: I replaced unique with shared. Without further ado, here is the output of the program:

    covariantReturnTypeSharedPtr

    Thanks to the remark from Alf P. Steinbach on the facebook group C++ Enthusiasts, I want to clarify. Both examples using the smart pointers std::unique_ptr and std::shared_ptr do not apply a covariant return type. The smart pointers have different dynamic types. This is, in this use case, sufficient to simulate the behavior of the first program covariantReturntype.cpp

    What’s Next?

    My next post is special. I will give an overview of posts I have written about idioms dealing with polymorphism and templates.

     

     

    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, Kris Kafka, 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, Dmitry Farberov, 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, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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

    Seminars

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

    Standard Seminars (English/German)

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

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    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 *