Post: Back to Basics: Initialization in C++
Notes from: https://www.youtube.com/watch?v=_23qmZtDBxg&list=PLqdbPkkHHWL7qB0wA7pePTTX4oI-xOsOu&index=8
Initialization Rules from C
- Scalers: simple / primitive types.
- Aggregtes: objects made of smaller pieces - an aggregate of smaller types.
- C does not have any initialisation rules.
Types of C initialization
scalar initialization:
int x = 3;
aggregate-initialisation:
- initialize an aggregate with brace-enclosed list
struct widget { int id; double price; }; widget w1 = {1000, 6.5};
copy-initialization:
- initialise from another compatible type
- only applies to structs/union => cannot copy init an array
struct widget {
int id;
double price;
};
widget w1 = {1000, 6.5};
widget w2 = w1;
without an explicity initializer:
- zero-initialized:
- when static or thread local duration
- initialized with value
0
for sclar, for aggregate eevery field is initialized with 0, pointer initialised to null
- unitialised (indeterminate value)
- access uninitialised object will result in undefined behaviour
If brace-enclosed list doesn’t contain an initialiser for every element:
- ie aggregate has 10 members but only 5 elements in braced initialiser
- remaining fields are zero initialised
C++ initialiser
C vs C+
- C++ an object lifetime begins are its been initialised
- C does not distinguish between storage and lifetime
Problems with aggregate initialisation
- C++ need initialisation rules to maintain class invariants
- we cannot allow string to be aggregate initialised
- ie: data = nullptr, size = 10
- Class authors can maintains class invariants through constructors
- rule: if a class has any (user provided) constructor, class is not an aggregate -> cannot be aggregate-initialised
Aggregate
Definition (C++20):
- no user-declared or inherited constructos
- inherited constructors: types with public base class are allowed to be aggregate
- no private or protected direct non-static data member
- no virtual, private or protected base classes
- no virtual function
Direct initialisation
- Providing constructor arguments in parenthesis
demo_str ds1("Hello", 5); demo_str ds2(ds1);
- can take place in the member initializer list of a constructor
C::C() : ds("James") // direct-init { }
- C++ allows direct-initialisation for scalar types
int x (5); double y (3.5);
- Direct initialisation invoking the copy constructor - as long as using parenthesis
demo_str ds1("Hello", 5); demo_str ds2(ds1);
Copy Initialisation
- Using an
=
with the RHS being a compatible typedemo_str ds3 = ds1;
- note: does not invoke the copy assignment but copy constructor
- passing an object by value
- returning an object by value
- throwing an exception
- catching an exception by value
Initialisation vs Assignment:
- Initialisation
- gives an object its initial state: direct-init / copy init
- Assignment
- overwriting the value of an existing object
Initialisation of members:
- type of initialisation of members depends on how the members are initialised in the constructor and not how the object is initialised
- when brace initialised an aggregate - each members are copy-initialised
struct widget { int id; demo_str ds; }; widget w1 = {1000, "Spock"};
Why do we care copy-initialisation vs direct-initialisation:
- copy-initialisation will not invoke an implicit converting constructor
void f(demo_str ds); // pass-by-value - copy init f("McCoy"); //copy-init
- direct-initialisation allow to invoke implicit converting constructor
f(demo_str("McCoy"));
Default Initialisation
- Initialise without any argument or
=
T t;
- C: scalar objects without initialiser is left uninitialised
int x;
- C++:
- scalar objects without initialiser is vacuous initialisation - initialisation that does nothing
- default constructor will be called
Value Initialisation
- initialise with empty parans - c++ most vexing parse
- empty paran can be used in member initialisation / RHS
- What value initialisation do:
- If
T
has a user-provided default constructor, call it- if default constructor provided, a member is not initialised and is a scaler type - remain uninitialised
- If
T
is an array, value initialise all of its elements - If
T
is a sclar, zero initialised - If
T
is a scalar, zero-initialised it - Other Value Initialisation is only allow if there no user-provided constructor of T
- zero-initialise the data member - initial all the members with 0
- default initialise the data member - the member might be a clas with default constructor
- If
- value initialising a class will recursively value initialise all the members only if no custom class construct is provided (default constructor)
- if a custom constructor is defined, it will not value initialise all members
C++03: hard to value initialise because of vexing parse
demo_str ds1; // default-init
demo_str ds2(); // vexing parse
demo_str ds3 = demo_str(); // value initialise - needs to be copy constructable
Uniform Initialisation Syntax
- All types allow initialisation with braces - works for sclars, aggregates, constructors
- Empty brace (
{}
) - easy way to perform value-initialisation - Empty brach
{}
will always value initialise (don’t need to worry about most vexing parse)demo_str ds3{}; // value-init calls demo_str
Direct List Initialisation
- Use braces to perform direct initialisation
int y{x};
Copy List Initialisation
=
with braces on the RHS- will not be permited if the constructor is explicit
int y = {5};
- allow converting constructor to be invoked
C c1{10, "Crusher"}; // ok c c2 = {10, "Crusher"}; // error, requires implicit conversion
Brace initialisation prevents narrowing conversion
- does not allow type conversion that result in loss of information
- with the exception of constexpr expression and compiler can check that there isn’t a loss of information
Initialiser List constructor
- allow direct-list init (not
=
) and copy list init (=
) - if a default constructor and
std::initialiser_list
constructor, the default constructor will be used - direct initialisation (
()
) vs direct list initialisation ({}
) will invoke different constructors ifstd::initializer_list
constructor is provided- only applies if the elements of
{}
are the same type
- only applies if the elements of
- do not add or remove initialiser list constructors
Initialization in Templates
- need to think of using direct initialisation (
std::initialization_list
constructor will not be called) or direct list initialisation (std::intialisation_list
constructor can be called)- sometime might prefer direct initialisation instead of direct initialisation list because a user can provide
std::initlization_list
explicitly
- sometime might prefer direct initialisation instead of direct initialisation list because a user can provide
Aggregate intialisation
- C++20 allow direct initialisation with
()
for aggregate types