trainings/AdvancedCppV2/Presentation/moder_cpp_cpp11.md

14 KiB

Cpp11

  • A quick reminder of lesser known features
    • static_assert
    • Uniform initialization
    • In-class initialization of non-static variables
    • initializer_list
    • alias
    • Template alias
    • C'tor inheritance
    • Attributes
    • Data structure alignment

static_assert

template <class T>
void swap(T& a, T& b)
{
    static_assert(std::is_copy_constructible<T>::value,
                  "Swap requires copying");
    static_assert(std::is_nothrow_move_constructible_v<T> &&
                  std::is_nothrow_move_assignable_v<T>);
    auto c = b;
    b = a;
    a = c;
}

Rationale: Preventing compilation on user defined conditions (usually specific types).

Performs compile-time assertion checking. Usually used with <type_traits> library.

The message is optional from C++17.


C++98/03 initialization


int a;                            // undefined value                           
int b(5);                         // direct initialization, b = 5              
int c = 10;                       // copy initialization, c = 10               
int d = int();                    // default initialization, d = 0             
int e();                          // function declaration - "most vexing parse"

int values[] = { 1, 2, 3, 4 };    // brace initialization of aggregate         
int array[] = { 1, 2, 3.5 };      // C++98 - ok, implicit type narrowing       

struct P { int a, b; };                                                        
P p = { 20, 40 };                 // brace initialization of POD               

std::complex<double> c(4.0, 2.0); // initialization of classes                 

std::vector<std::string> names;   // no initialization for list of values      
names.push_back("John");                                                       
names.push_back("Jane");                                                       

C++11 initialization with


int a;                               // still undefined value                    
int b{5};                            // brace initialization, b = 5              
int c{};                             // brace initialization, c = 0              

int values[] = { 1, 2, 3, 4 };       // brace initialization of aggregate        
int array[] = { 1, 2, 3.5 };         // C++11: error - implicit type narrowing   

struct P { int a, b;                                                             
P p = { 20, 40 };                    // brace initialization of POD              

std::complex<double> c{4.0, 2.0};    // brace initialization calls adequate c-tor

std::vector<std::string> names = { "John", "Jane" };                             
                                    // brace initialization of vector           

Rationale: eliminate problematic initialization cases from C++98, initialization of STL containers, have one universal way of initialization.


In-class initialization of non-static variables

struct Foo
{
    Foo() {}
    Foo(std::string a) : a_(a) {}
    void print() { std::cout << a_ << std::endl; }

private:
   std::string a_ = "Foo";              // C++98: error, C++11: OK
   static const unsigned VALUE = 20u;   // C++98: OK, C++11: OK
};

Foo().print();          // Foo
Foo("Bar").print();     // Bar

std::initializer_list<T>

auto values = {1, 2, 3, 4, 5};   // values is std::initializer_list<int>
std::vector<int> v = {1, 2, -3}; // creates a vector from
                                 // std::initializer_list<int>
  • Defined in initializer_list header
  • Elements are kept in an array
  • Elements are immutable
  • Elements must be copyable
  • Have limited interface and access via iterators - begin(), end(), size()
  • Should be passed to functions by value

Constructor priority


template<class Type>                                               
class Bar {                                                        
    std::vector<Type> values_;                                     
public:                                                            
    Bar(std::initializer_list<Type> values) : values_(values) {}   
    Bar(Type a, Type b) : values_{a, b} {}                         
};                                                                 

Bar<int> c = {1, 2, 5, 51};   // calls std::initializer_list c-tor
Bar<int> d{1, 2, 5, 51};      // calls std::initializer_list c-tor
Bar<int> e = {1, 2};          // calls std::initializer_list c-tor
Bar<int> f{1, 2};             // calls std::initializer_list c-tor
Bar<int> g(1, 2);             // calls Bar(Type a, Type b) c-tor  
Bar<int> h = {};              // calls std::initializer_list c-tor
                       // or default c-tor if exists
Bar<std::unique_ptr> c = {new int{1}, new int{2}};                
// error - std::unique_ptr is non-copyable                        

C-tor with std::initializer_list has greater priority, even if other c-tors match.


Exercise 1

  • Open project streamer
  • Add two C'tor:
    • first will take initializer_list
    • second const StreamInfo&
  • initialize vector in initialization list

Type aliasing

typedef std::ios_base::fmtflags Flags;
using Flags = std::ios_base::fmtflags;  // the same as above
Flags fl = std::ios_base::dec;
typedef std::vector<std::shared_ptr<Socket>> SocketContainer;
std::vector<std::shared_ptr<Socket>> typedef SocketContainer; // correct ;)
using SocketContainer = std::vector<std::shared_ptr<Socket>>;

Rationale: More intuitive alias creation.

A type alias is a name that refers to a previously defined type. It could be created with typedef. From C++11 type aliases should be created with using keyword.


Template aliases

struct ThemedLabelToogleButton { ... }

template <typename T>
using ButtonMap = std::map<ThemedLabelToogleButton, T>;

ButtonMap<std::function<void()>> my_map; 
// std::map<ThemedLabelToogleButton, std::function<void()>

Type alias can be parametrized with templates. It was impossible with typedef.

Template aliases cannot be specialized.


Constructors inheritance

struct A {
    explicit A(int);
    int a;
};

struct B : A {
    using A::A;  // implicit declaration of B::B(int)
    B(int, int); // overloaded inherited Base ctor
};
  • Derived class constructors are generated implicitly, only if they are used
  • Derived class constructors take the same arguments as base class constructors
  • Derived class constructor calls according base class constructor
  • Constructor inheritance in a class that adds a new field might be risky - new fields can be uninitialized

Exercise 2

  • Open project streamer
  • Add aliases for ip adress, port and vlan.

Attributes


Standard attributes

  • [[noreturn]] - function does never return, like std::terminate. If it does, we have UB
  • [[deprecated]] (C++14) - function is deprecated
  • [[deprecated("reason")]] (C++14) - as above, but compiler will emit the reason
  • [[fallthrough]] (C++17) - in switch statement, indicated that fall through is intentional
  • [[nodiscard]] (C++17) - you cannot ignore value returned from function
  • [[maybe_unused]] (C++17) - suppress compiler warning on unused class, typedef, variable, function, etc.

[[noreturn]] attribute

[[noreturn]] void f() {
    throw "error";
    // OK
}

[[noreturn]] void q(int i) {
    if (i > 0) {
        throw "positive";
    }
    // the behavior is undefined if called with argument <=0
}

[[deprecated]] attribute

Attributes for namespaces and enumerators are available from C++17.

[[deprecated("Please use f2 instead")]] int f1();

enum E {
    foo = 0,
    bar [[deprecated]] = foo
};
E e = bar; // Emits warning

namespace [[deprecated]] old_stuff {
    void legacy();
}
old_stuff::legacy(); //Emits warning

[[fallthrough]] attribute

void f(int n){
    void g(), h(), i();
    switch(n) {
    case 1:
    case 2:
        g();
        [[fallthrough]];
    case 3: // no warning on fallthrough
        h();
    case 4: // compiler may warn on fallthrough
        i();
        [[fallthrough]]; // illformed, not before a case label
    }
}

[[nodiscard]] attribute

struct [[nodiscard]] error_info {};
error_info process(Data*);

// ...

void passMessage() {
    auto data = getData();
    process(data);  // compiler warning, discarding error_info
}

[[maybe_unused]] attributes

[[maybe_unused]] void f([[maybe_unused]] bool thing1,
                        [[maybe_unused]] bool thing2)
{
  [[maybe_unused]] bool b = thing1 && thing2;
  assert (b); // in release mode, assert is compiled out, and b is unused
              // no warning because it is declared [[maybe_unused]]
} // parameters thing1 and thing2 are not used, no warning