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.
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.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
The output of the program is as expected.
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 w
indow
. 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.
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:
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, 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,and Matt Godbolt.
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)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!