Modern C++
Standard Practices from Standard Library


1. Course Introduction and Compiler Enhancements


Stefano Lusardi, Software Developer

Course introduction


  • Self contained modules: no dependencies
  • Practical focus: Code samples
  • C++ devs: share, update, get in touch
  • Non-C++ devs: insights and feature discovery

Modern C++ Practices


Modern: C++11 Standard and beyond
Practices: readable, expressive, solid, reusable pieces of code

  • expressive: self-explicative
  • solid: widely tested
  • reusable: D.R.Y. (Don't Repeat Yourself)


e.g.: std::count. Holds all the 3 principles

Standards


  • What is a standard (library)?
  • Containers, Iterators, Algorithms

  • Why do we need to have new standards?
  • General purpose

We will discuss only STL although Boost, Qt and others third party provides even wider set of features


The simplest example: loop comparison


Non-standard vs. standard implementation in practice


Remember that we are looking for:

  • Expressivity: clear meaning, easy to understand
  • Testability: easy to test, or better, already tested
  • Reusability: have room for new features on top


Example: "Old school" for loop


int c = 0;
for (int i = 0; i < myVec.size(); ++i) {
    myVec[i] == 5 && ++c;
}
						

How many times have you seen something like this (but ten times more complex)?

To fully understand this snippet you need at least to read it once entirely


Example: "New school" standard algorithm



int c = std::count(begin(myVec), end(myVec), 5);
						


Isn't it clearer, shorter, more expressive and bullet proof?

You can understand its meaning even without read it entirely!

Modern Compiler


What does your (modern) compiler do for you?

Modern Compiler


Automatic generation of Special Member Functions

  1. Default Constructor (only if no constructor generated by the user*)
  2. Destructor
  3. Copy Constructor (if no user defined 5, 6)
  4. Copy Assignment (if no user defined 5, 6)
  5. Move Constructor (if no user defined 2, 3, 4, 6)
  6. Move Assignment (if no user defined 2, 3, 4, 5)

*Note that even copy constructor counts as user generated

All Special Member Functions


class Foo{
	Foo();                      // 1. Default Constructor
	~Foo();                     // 2. Destructor
	Foo(const Foo&);            // 3. Copy Constructor
	Foo& operator=(const Foo&); // 4. Copy Assignment
	Foo(Foo&&);                 // 5. Move Constructor
	Foo& operator=(Foo&&);      // 6. Move Assignment
};
						

Partial Special Member Functions


// What you write:
class Bar {
    Bar(const Bar& bar, int i = 0);
    // This counts as user defined copy constructor since 
    // the second argument is defaulted
};
// What you get:
class Bar {
    //1. No Default Constructor: we have a user defined ctor
    ~Bar();                     // 2. Destructor
    Bar(const Bar&);            // 3. Copy Constructor
    Bar& operator=(const Bar&); // 4. Copy Assignment
    // 5. No Move Constructor: we have a user defined copy ctor
    // 6. No Move Assignment: we have a user defined copy ctor
};
						


Hard to remember uh?

Use the Rule of 3 (or 5):

"If a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three."

from cppreference.com


Note that in case you want to have deep copy instead of shallow copy you must anyway implement your own copies

Modern Compiler

default and delete specifiers


  • Defaulted function counts as a user defined function
  • Deleted function counts as a user defined function


	
struct Foo{
    Foo(double radius) { }
    Foo(Foo& c) = delete;
    Foo& operator=(Foo& c) = delete;
};

int main(){
    Foo f1;      // KO: No default ctor
    Foo f2(2);   // -OK-
    f2 = f2;     // KO: No copy assignament
    Foo f3(f2);  // KO: No copy ctor
    Foo f4 = f2; // KO: No copy ctor
    return 0;
}
						

Do you still need a private copy ctor/assignment?
No, just delete them!

Modern Compiler

Explicit constructor specifier

	
struct Circle{
    // Not Explicit ctor
    Circle(double radius) : mRadius{radius} { }
    double Area() const { return mRadius * mRadius * PI; }
    double mRadius;
};	

struct Square{
    // Explicit ctor
    explicit Square(double side) : mSide{side} { }
    double Area() const { return mSide * mSide; }
    double mSide;
};
						



double Area(Circle c){ return c.Area(); }

double Area(Square s){ return s.Area(); }

int main(){
    Circle c(4);
    Square s(4);
    
    std::cout << "Circle Area: " << Area(c) << "\n";
    std::cout << "Square Area: " << Area(s) << "\n";
    std::cout << "Which Area?: " << Area(4) << "\n";
    
    return 0;
}
						

Modern Compiler

RVO (Return Value Optimization)

Generating useless copies: why do we care?


g++ -std=c++11 -fno-elide-constructors -O2 -pedantic -pthread main.cpp && ./a.out example on coliru.com


struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  C c = f();
}
						


Note that Move Semantics will be treated in depth in the next talk

Thank you!

What's next?


Move Semantics and Smart Pointers

Questions?