Covariant Return Type

Contents[Show]

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.

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

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

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, Animus24, 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, Matthieu Bolt, 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, and Rob North.

 

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

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

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.

  • 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++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

Comments   

0 #1 Sam W 2023-02-27 18:06
For the unique_ptr and shared_ptr examples, is it still covariant, because they all return the same type? Namely, unique_ptr.

Is it possible to have DefaultWindow and FancyWindow return unique_ptr and unique_ptr respectively? I would think not, because unique_ptr is not a subclass of unique_ptr.

This is one feature of the "naive" implementation I don't see possible with the smart-pointer version.
Quote
0 #2 yaz 2023-04-08 00:30
Nice article, you could also use the CRTP (Curiously Recurring Template Pattern) with std::unique_ptr for doing

here is an example in the following godbolt link:
https://godbolt.org/z/evhhcsohn
Quote

Stay Informed about my Mentoring

 

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

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

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 4501

Yesterday 6193

Week 10694

Month 32368

All 12110577

Currently are 172 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments