static_assert allows you to check at compile time if a type
T fulfills the
Before I dive into concepts in my next post, I want to motivate their use.
When I discuss move semantics in my classes, I introduce the idea of the Big Six. The Big Six control the life cycle of objects: creation, copy, move, and destruction. Here are the six special member functions for a type
- Default constructor:
- Copy constructor:
- Copy assignment:
operator = (const X&)
- Move constructor:
- Move assignment:
operator = (X&&)
By default, the compiler can generate the Big Six if needed. You can define the six special member functions but can also ask explicitly the compiler to provide them with
= default or delete them with
=delete. If you can, you should avoid defining any default operations. This rule is also known as the rule of zero. That means that you can avoid writing any custom constructor,
copy/move constructors, assignment operators, or destructors using types that support the appropriate copy/move semantics. This applies to the built-in types
double, but also the containers of the standard template library, such as
The default construction and the copy construction work because they are already defined for
std::map. When the compiler auto-generates, for example, the copy constructor for a class, it invokes the copy constructor for all class members and bases.
The fun starts when you define
=delete one of the special member functions because the Big Six are closely related. Due to this relation, you have to define, or
=delete all six. Consequently, this rule is called the rule of six. Sometimes, you hear the rule of five because the default constructor is special and, sometimes excluded. Let me weaken this rule: When you define or =delete any default operation, you must consider all six. Defining a special member can mean both: You implement the particular member function, or you request it from the compiler using
I wrote that the compiler could generate the Big Six if needed. Now, I have to clarify what I mean: This is only true if you don’t define or
=delete any special member function because there are pretty sophisticated dependencies between the six special member functions.
Here is the point when my class discussions start: How can I be sure that my type X supports move semantics? Of course, you want that your type X supports move semantics.
Move semantics has two big benefits:
- Cheap move operations are used instead of expensive copy operations
- Move operations require no memory allocation and, therefore, a
std::bad_allocexception is not possible
There are essentially two ways to check if a type supports the Big Six: Study the dependencies, or define and use a concept
First. let’s study the dependencies between the Big Six:
Dependencies between the Big Six
Howard Hinnant developed in his talk at the ACCU 2014 conference an overview of the automatically generated special member functions. Here is a screenshot of his completel table:
Howard’s table demands a profound explanation.
First of all, user-declared means for one of these six special member functions that you define it explicitly or request it from the compiler with
=default. Deletion of the special member function with
=delete is also regarded as defined. Essentially, when you use the name, it counts as user-declared.
When you define any constructor, you get no default constructor. A default constructor is a constructor which can be invoked without an argument.
When you define or delete a default constructor with
=delete, no other of the six special member functions are affected.
When you define or delete a destructor, a copy constructor, or a copy assignment operator with
=delete, you get no compiler-generated move constructor and move assignment operator. This means move operations, such as move construction or move assignment fall back to copy operations, such as copy construction or copy assignment. This fallback automatism is marked red in the table. Additionally, the red-marked copy operations are deprecated.
When you define or delete with
=delete a move constructor or a move assignment operator, you get only the defined
=delete move constructor or move assignment operator. Consequently, the copy constructor and the copy assignment operator are set to
=delete. Invoking a copy operation such as copy construction or copy assignment causes a compilation error.
Due to this dependency hell, I give the following general rule in my classes: Make your user-defined types as simple as possible and go for abstraction. Let the compile do the complicated stuff.
Go for Abstraction
Here are a few consequences of this rule:
- Don’t declare any special member function if necessary.
- Use a
std::arrayinstead of a C-array in your class. A
std::arraysupports the Big Six.
- Use a
std::shared_ptrin a class, but not a raw pointer. The compiler-generated copy constructor and copy assignment operator for a raw pointer makes a shallow copy but not a deep copy. This means only the pointer is copied but not its content. Using a
std::shared_ptrin a user-defined type directly expresses your intent. A
std::unique_ptrcannot be copied; therefore, the class cannot be copied. A
std::shared_ptr, and, therefore, the class can be copied.
- If you have a user-defined type in your class that disables the auto-generation of the Big Six, you have two options. Implement the special member functions for this user-defined type, or refactor your class into two classes. Don’t let one user-defined type infect your class design.
Let me end my general rule with an anecdote: I once did a code review for a friend. He asked me to analyze his code before it went into production. He used a union in this central class. I call this class encapsulating the union for simplicity
WrapperClass. The used union was a so-called tagged union. Meaning that the
WrapperClass keeps track of the currently used type of the union. To know more about unions, read my previous post, “C++ Core Guidelines: Rules for Unions“. Finally, the
WrapperClass consisted of about 800 lines of code to support the Big Six. He had to implement the six special member functions in eight variations because the union could have eight different types. Additionally, he implemented a few convenience functions to compare instances of
WrapperClass. When I analyzed the class, it was immediately clear: this is a code smell and a reason for refactoring. I asked him if he could use C++17. The answer was yes, and I replaced the union with a std::variant. Additionally, I added a generic constructor. The result was that the
WrapperClass went from 800 to 40 lines of code.
std::variant supports the six special member functions and the six comparison operators by design.
You may not want to study the dependencies between the six special member functions. In my next post, I will continue this story and define and use the concept BigSix to decide at compile time if a given type supports all six special member functions.
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, 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, Rob North, Bhavith C Achar, and Marco Parri Empoli.
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|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
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++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,