class SomeType {
int number;
std::string name;
public:
SomeType(int number, std::string name)
: number(number)
, name(name)
{}
SomeType(int number)
: SomeType(number, "foo")
{}
SomeType(std::string name)
: SomeType(42, name)
{}
SomeType()
: SomeType(42, "foo")
{}
};
Non-modern C++ did not allow constructors to call each other.
Starting with C++11, constructors can delegate calls to other constructors inside the same class.
In a corresponding exercise, you'll use this to refactor code.
#include <iostream>
struct A {
// constructor
A()
: name("hello")
{
std::cout << "constructor";
}
// destructor
~A() {
std::cout << "destructor";
}
// copy constructor
A(const A& other)
: name(other.name)
{
std::cout << "copy constructor";
}
// copy assignment operator
A& operator= (const A& other) {
name = other.name;
std::cout << "copy assignment";
return *this;
}
std::string name;
};
int main(int argc, char** argv) {
A var1;
A var2 = var1;
var1 = var2;
}
#include <iostream>
struct A {
// constructor
A()
: name("hello")
{
std::cout << "constructor";
}
// destructor
~A() {
std::cout << "destructor";
}
// copy constructor
A(const A& other)
: name(other.name)
{
std::cout << "copy constructor";
}
// copy assignment operator
A& operator= (const A& other) {
name = other.name;
std::cout << "copy assignment";
return *this;
}
std::string name;
};
int main(int argc, char** argv) {
A var1;
A var2 = var1;
var1 = var2;
}
// output:
// constructor
// copy constructor
// copy assignment
// destructor
// destructor
Default constructors initialize each member in order of declaration using their default constructor.
Default destructors destruct members in reverse order of their declaration.
Default copy constructors, copy assignment operators, move constructors and move assignment operators perform member-wise copy/move, respectively.
If you define one of the following, you probably need to define all three:
If you define one of the following, you probably need to define all three:
struct Interface {
// compiler generated
// virtual destructor
virtual ~Interface() = default;
virtual void method() = 0;
};
class POD {
public:
// compiler generated default constructor
// this is needed because
// defining another constructor
// causes this one not to be generated
POD() = default;
POD(const std::string& name)
: name(name)
{}
private:
std::string name;
}
Default-generated methods are guaranteed to be inlineable by compilers (performance gains).
// stolen from boost
struct noncopyable {
noncopyable() = default;
~noncopyable() = default;
noncopyable(
const noncopyable&) = delete;
noncopyable& operator=(
const noncopyable&) = delete;
}
"Program to an 'interface', not an 'implementation'."
(page 18)
Composition over inheritance: "Favor 'object composition' over 'class inheritance'."
(page 20)
struct Base
{
virtual void foo();
};
struct A : Base
{
// A::foo is final
void foo() final;
// Error: non-virtual function cannot be final
void bar() final;
};
// struct B is final
struct B final : A
{
// Error: foo cannot be overridden as it's final in A
void foo();
};
// Error: B is final
struct C : B
{
};
1. used in classes to prevent inheritance
2. used in methods to prevent overloading by derived classes
struct A
{
virtual void foo();
void bar();
};
struct B : A
{
// Error: B::foo does not override A::foo
void foo() const final override;
// OK: B::foo overrides A::foo
void foo() final override;
// Error: A::bar is not virtual
void bar() override;
};
override is used to statically ensure that a method is really overriding a base classes virtual method
std::vector<int> vec = {1, 2, -4, 12};
for(const auto i : {1, 6, 12, -8, 15}) {
std::cout << i << std::endl;
}
template <typename T>
class MyContainer {
MyContainer(std::initializer_list<T> t) {
for(const auto& element : t) {
// do something with element
}
}
}
widget w; // (a)
widget w(); // (b)
widget w{}; // (c)
widget w(x); // (d)
widget w{x}; // (e)
widget w = x; // (f)
widget w = {x}; // (g)
auto w = x; // (h)
auto w = widget{x}; // (i)
What is the difference between those?
widget w; // (a)
w is constructed using the default constructor of class widget
or
w is uninitialized iff widget is a built in type
widget w(); // (b)
widget w{}; // (c)
(b) is actually a function declaration
(c) was introduced in C++11 and guarantees the default constructor is called
(even if there is a constructor taking std::initializer_list as an argument)
widget w(x); // (d)
widget w{x}; // (e)
(d) and (e) initialize widget by calling its constructor, passing x
If x is a type, (d) is actually a function declaration, even if there is a variable x in scope
(e) is never a function declaration
widget w(x); // (d)
widget w{x}; // (e)
(e) avoids narrowing:
struct A {
A(int n) : number(n) {}
int number;
}
// ok, implicit conversion
A one(1.23);
// compiler error, narrowing
A two{1.23};
widget w = x; // (f)
widget w = {x}; // (g)
(f) is copy initialization, (g) is copy list initialization no assignment operator is involved
The same reasons apply to prefer (g) over (f) as with (e) over (d)
If x is not of type widget, implicit conversions can occur with (f), but not with (g).
auto w = x; // (h)
auto w = widget{x}; // (i)
semantics are the same as with (f) and (g), but there are less ways to shoot yourself in the foot
(h) never converts types, guaranteed to only call a single copy constructor
(i) allows you to commit to a type, but avoid narrowing conversions
widget w; // (a)
widget w(); // (b)
// prefer this
widget w{}; // (c)
widget w(x); // (d)
widget w{x}; // (e)
widget w = x; // (f)
widget w = {x}; // (g)
// prefer these
auto w = x; // (h)
auto w = widget{x}; // (i)
struct A {
A() = default;
A(const std::string& name)
: name(name)
{}
std::string name = "42";
}
A one;
auto two = A{"hello"};
std::cout << one.name << std::endl;
std::cout << two.name << std::endl;
struct A1{};
struct A2{};
struct A : A2, A1{};
struct B {};
struct D : A, B {
D()
: name("foo")
, a({13})
{
a = {11};
name = "bar";
}
std::string name
= "abc";
std::vector<int> a
= {3};
};
Whiteboard exercise: name all called constructors and their arguments in the right order.