File: memberinit.yo

package info (click to toggle)
c%2B%2B-annotations 12.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 13,044 kB
  • sloc: cpp: 24,337; makefile: 1,517; ansic: 165; sh: 121; perl: 90
file content (105 lines) | stat: -rw-r--r-- 4,196 bytes parent folder | download | duplicates (3)
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
Non-static data members of classes are usually initialized by the class's
constructors. Frequently (but not always) the same initializations are used by
different constructors, resulting in multiple points where the initializations
are performed, which in turn complicates class maintenance.

Consider a class defining several data members: a pointer to data, a data
member storing the number of data elements the pointer points at, a data
member storing the sequence number of the object. The class also
offer a basic set of constructors, as shown in the following class interface:
        verb(    class Container
    {
        Data *d_data;
        size_t d_size;
        size_t d_nr;

        static size_t s_nObjects;

        public:
            Container();
            Container(Container const &other);
            Container(Data *data, size_t size);
            Container(Container &&tmp);
    };)

The initial values of the data members are easy to describe, but somewhat
hard to implement. Consider the initial situation and assume the default
constructor is used: all data members should be set to 0, except for tt(d_nr)
which must be given the value tt(++s_nObjects). Since these are
em(non)-default actions, we can't declare the default constructor using tt(=
default), but we must provide an actual implementation:
            verb(    Container()
    :
        d_data(0),
        d_size(0),
        d_nr(++s_nObjects)
    {})

In fact, em(all) constructors require us to state the
tt(d_nr(++s_nObjects)) initialization. So if tt(d_data)'s type would have been
a (move aware) class type, we would still have to provide implementations for
all of the above constructors.

    bf(C++), however, also supports 
        hi(data member: initialization)em(data member initializers),
simplifying the initialization of non-static data members. Data member
initializers allow us to assign initial values to data members. The compiler
must be able to compute these initial values from initialization expressions,
but the initial values do not have to be constant expressions. So
tt(++s_nObjects) can be an initial value.

    Using data member initializers for the class tt(Container) we get:
        verb(    class Container
    {
        Data *d_data = 0;
        size_t d_size = 0;
        size_t d_nr = ++s_nObjects;

        static size_t s_nObjects;

        public:
            Container() = default;
            Container(Container const &other);
            Container(Data *data, size_t size);
            Container(Container &&tmp);
    };)

Note that the data member initializations are recognized by the compiler,
and are applied to its implementation of the default constructor. In fact, all
constructors will apply the data member initializations, unless explicitly
initialized otherwise. E.g., the move-constructor may now be implemented like
this:
        verb(    Container(Container &&tmp)
    :
        d_data(tmp.d_data),
        d_size(tmp.d_size)
    {
        tmp.d_data = 0;
    })

Although tt(d_nr)'s intialization is left out of the implementation it
em(is) initialized due to the data member initialization provided in the
class's interface.

    An emi(aggregate) is an array or a tt(class) (usually a tt(struct) with no
user-defined constructors, no private or protected non-static data members,
no base classes (cf. chapter ref(INHERITANCE)), and no virtual functions
(cf. chapter ref(POLYMORPHISM))). E.g.,
        verb(    struct POD      // defining aggregate POD
    {
        int first = 5; 
        double second = 1.28; 
        std::string hello{ "hello" };
    };)

To initialize such aggregates em(braced initializer lists) can be used. In
fact, their use is preferred over using the older form (using parentheses), as
using braces avoids confusion with function declarations. E.g.,
        verb(    POD pod{ 4, 13.5, "hi there" };)

When using  braced-initializer lists not all data members need to be
initialized. Specification may stop at any data member, in which case the
default (or explicitly defined initialization values) of the remaining data
members are used. E.g.,
        verb(    POD pod{ 4 };   // uses second: 1.28, hello: "hello")