1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
When defining variables and objects they may immediately be given
initial values. Class type objects are always initialized using one of their
available constructors. bf(C) already supports the array and struct
hi(initialization)emi(initializer list) consisting of a list of constant
expressions surrounded by a pair of curly braces.
bf(C++) supports a comparable initialization, called
emi(uniform initialization). It uses the following syntax:
verb( Type object{ value list };)
When defining objects using a list of objects each individual object
may use its own uniform initialization.
The advantage of uniform initialization over using constructors is that using
constructor arguments may sometimes result in an ambiguity as constructing an
object may sometimes be confused with using the object's overloaded function
call operator (cf. section ref(FUNOBJ)). As initializer lists can only be used
with em(plain old data) (POD) types (cf. section ref(POD)) and with classes
that are `initializer list aware' (like tt(std::vector)) the ambiguity does
not arise when initializer lists are used.
Uniform initialization can be used to initialize an object or
variable, but also to initialize data members in a constructor or implicitly
in the return statement of functions. Examples:
verb( class Person
{
// data members
public:
Person(std::string const &name, size_t mass)
:
d_name {name},
d_mass {mass}
{}
Person copy() const
{
return {d_name, d_mass};
}
};)
Object definitions may be encountered in unexpected places, easily resulting in
(human) confusion. Consider a function `tt(func)' and a very simple
class tt(Fun) (tt(struct) is used, as data hiding is not an issue here;
in-class implementations are used for brevity):
verb( void func();
struct Fun
{
Fun(void (*f)())
{
std::cout << "Constructor\n";
};
void process()
{
std::cout << "process\n";
}
};)
Assume that in tt(main) a tt(Fun) object is defined as follows:
verb( Fun fun(func);)
Running this program displays tt(Constructor), confirming that the
object tt(fun) is constructed.
Next we change this line of code, intending to call tt(process) from an
anonymous tt(Fun) object:
verb( Fun(func).process();)
As expected, tt(Constructor) appears, followed by the text tt(process).
What about just defining an anonymous tt(Fun) object? We do:
verb( Fun(func);)
Now we're in for a surprise. The compiler complains that tt(Fun)'s default
constructor is missing. Why's that? Insert some blanks immediately after
tt(Fun) and you get tt(Fun (func)). Parentheses around an identifier are OK,
and are stripped off once the parenthesized expression has been parsed. In
this case: tt((func)) equals tt(func), and so we have tt(Fun func): the
definition of a tt(Fun func) object, using tt(Fun)'s default constructor (which
isn't provided).
So why does tt(Fun(func).process()) compile? In this case we have a member
selector operator, whose left-hand operand must be an class-type object. The
object must exist, and tt(Fun(func)) represents that object. It's not the name
of an existing object, but a constructor expecting a function like tt(func)
exists. The compiler now creates an anonymous tt(Fun), passing it tt(func) as
its argument.
Clearly, in this example, parentheses cannot be used to create an anonymous
tt(Fun) object. However, the uniform initialization em(can) be used. To define
the anonymous tt(Fun) object use this syntax:
verb( Fun{ func };)
(which can also be used to immediately call one of its members. E.g.,
tt(Fun{func}.process())).
Although the uniform intialization syntax is slightly different from the
syntax of an initializer list (the latter using the assignment operator) the
compiler nevertheless uses the initializer list if a constructor
supporting an initializer list is available. As an example consider:
verb( class Vector
{
public:
Vector(size_t size);
Vector(std::initializer_list<int> const &values);
};
Vector vi = {4};)
When defining tt(vi) the constructor expecting the initializer list is
called rather than the constructor expecting a tt(size_t) argument. If the
latter constructor is required the definition using the standard constructor
syntax must be used. I.e., tt(Vector vi(4)).
Initializer lists are themselves objects that may be constructed using
another initializer list. However, values stored in an initializer list are
immutable. Once the initializer list has been defined their values remain
as-is.
Before using initializer lists the
tt(initializer_list)hi(initializer list) header file must be included.
Initializer lists support a basic set of member functions and constructors:
itemization(
ithtq(initializer_list)(initializer_list<Type> object)
(defines tt(object) as an empty initializer list)
ittq(initializer_list<Type> object { list of Type values })
(defines tt(object) as an initializer list containing tt(Type) values)
ittq(initializer_list<Type> object(other))
(initializes tt(object) using the values stored in tt(other))
ithtq(size)(size_t size() const)
(returns the number of elements in the initializer list)
ithtq(begin)(Type const *begin() const)
(returns a pointer to the first element of the initializer list)
ithtq(end)(Type const *end() const)
(returns a pointer just beyond the location of the last element of the
initializer list)
)
|