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
|
Conversions are not only performed by conversion operators, but also by
constructors accepting one argument (i.e., constructors having one or multiple
parameters, specifying default argument values for all parameters or for all
but the first parameter).
Assume a data base class tt(DataBase) is defined in which tt(Person)
objects can be stored. It defines a tt(Person *d_data) pointer, and so it
offers a copy constructor and an overloaded assignment operator.
In addition to the copy constructor tt(DataBase) offers a default
constructor and several additional constructors:
itemization(
itt(DataBase(Person const &)): the tt(DataBase) initially contains a
single tt(Person) object;
itt(DataBase(istream &in)): the data about multiple persons are read from
tt(in).
itt(DataBase(size_t count, istream &in = cin)): the data of tt(count)
persons are read from tt(in), by default the standard input stream.
)
The above constructors all are perfectly reasonable. But they also allow
the compiler to compile the following code without producing any warning at
all:
verb( DataBase db;
DataBase db2;
Person person;
db2 = db; // 1
db2 = person; // 2
db2 = 10; // 3
db2 = cin; // 4)
Statement 1 is perfectly reasonable: tt(db) is used to redefine
tt(db2). Statement 2 might be understandable since we designed tt(DataBase) to
contain tt(Person) objects. Nevertheless, we might question the logic that's
used here as a tt(Person) is not some kind of tt(DataBase). The logic becomes
even more opaque when looking at statements 3 and 4. Statement 3 in effect
waits for the data of 10 persons to appear at the standard input
stream. Nothing like that is suggested by tt(db2 = 10).
Implicit i(promotion)s are used with statements 2 through 4. Since
constructors accepting, respectively a tt(Person), an tt(istream), and a
tt(size_t) and an tt(istream) have been defined for tt(DataBase) and since the
assignment operator expects a tt(DataBase) right-hand side (rhs) argument the
compiler first converts the rhs arguments to anonymous tt(DataBase) objects
which are then assigned to tt(db2).
It is good practice to prevent implicit promotions by using the
ti(explicit) modifier when declaring a constructor. Constructors using the
tt(explicit) modifier can only be used to construct objects
explicitly. Statements 2-4 would not have compiled if the constructors
expecting one argument would have been declared using tt(explicit). E.g.,
verb( explicit DataBase(Person const &person);
explicit DataBase(size_t count, std:istream &in);)
Having declared all constructors accepting one argument as tt(explicit)
the above assignments would have required the explicit specification of
the appropriate constructors, thus clarifying the programmer's intent:
verb( DataBase db;
DataBase db2;
Person person;
db2 = db; // 1
db2 = DataBase{ person }; // 2
db2 = DataBase{ 10 }; // 3
db2 = DataBase{ cin }; // 4)
As a i(rule of thumb) prefix one argument constructors with the
tt(explicit) keyword unless implicit promotions are perfectly natural
(tt(string)'s tt(char const *) accepting constructor is a case in point).
|