-
Notifications
You must be signed in to change notification settings - Fork 82
Classes and Structs
POD/Aggregate preferred (shall be struct
, otherwise shall be class
and always specify rule of 5/6 constructors (ok to = default
or = deleted
, but always make explicit).
struct My_aggregate_type
{
int my_member{7}; // use direct member initializers if necessary
};
class My_non_aggregate_type
{
// always specify public, protected, private in this order
public:
// Rule-of-six is usually desired
My_non_aggregate_type() = default;
// Rule-of-five
constexpr My_non_aggregate_type(My_non_aggregate_type const &) = default;
constexpr My_non_aggregate_type(My_non_aggregate_type &&) = default;
constexpr My_non_aggregate_type & operator =(My_non_aggregate_type const &) = default;
constexpr My_non_aggregate_type & operator =(My_non_aggregate_type &&) = default;
protected:
// make destructor protected to prevent dynamic polymorphism (unless explicitly desired)
constexpr ~My_non_aggregate_type() = default;
private:
int my_member{7}; // use direct member initializers if necessary
};
We always use static polymorphism, unless we actually require run-time decisions (more on this in the section below).
Static polymorphism is implemented via C++ concepts. Instead of designing a class, you first abstract the class's public interface and make a concept of it. Algorithms and other free function than work on general types (as templates) that are constrained via more or less specialized concepts.
#include <iostream>
#include <type_traits>
template<typename T>
concept bool Number = std::is_arithmetic<T>::value;
template<typename T>
concept bool Integral = Number<T> && std::is_integral<T>::value;
Number doubleTheValue(Number const in)
{
std::cerr << "general funcion!\n";
return in * 2;
}
Integral doubleTheValue(Integral const in)
{
std::cerr << "special funcion!\n";
return (in << 1);
}
void print2(Number const & v)
{
std::cout << doubleTheValue(v) << '\n';
}
int main()
{
print2(double{0.2}); // general case
print2(int{2}); // specialized case
print2(long{2}); // specialized case
return 0;
}
In some cases you just don't know which function can be picked at compile-time, e.g. the exact input-file type cannot be known. In these cases you can use an tag-dispatching over an inner type that also follows a concept for these specializations. Tag-dispatching has become quite easy in C++17 with std::visit.