Skip to content

Classes and Structs

Hannes Hauswedell edited this page Feb 23, 2017 · 19 revisions

General Design

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
};

Static polymorphism (via concepts)

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;
}

Dynamic polymorphism

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.

Clone this wiki locally