mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-18 10:06:54 +01:00
WIP operator overloading
git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@312 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
parent
525d470554
commit
b5a556f01e
2 changed files with 70 additions and 128 deletions
|
@ -9,12 +9,13 @@ lsect(EXTRACTORS)(Overloading the insertion and extraction operators)
|
|||
includefile(overloading/insertextract)
|
||||
|
||||
COMMENT(>>>>>>>>>>>>> NEXT <<<<<<<<<<<<<)
|
||||
lsect(EXPLICIT)(The keyword `explicit')
|
||||
includefile(overloading/explicit.yo)
|
||||
|
||||
lsect(ConversionOperators)(Conversion operators)
|
||||
includefile(overloading/conversion)
|
||||
|
||||
COMMENT(>>>>>>> Done <<<<<<<<)
|
||||
lsect(EXPLICIT)(The keyword `explicit')
|
||||
includefile(overloading/explicit.yo)
|
||||
|
||||
subsect(Explicit conversion operators (C++0x))
|
||||
includefile(overloading/explicitconversion)
|
||||
|
||||
|
|
|
@ -1,133 +1,74 @@
|
|||
COMMENT(<<<<<<<<< FROM MEMORY: PROMOTIONS
|
||||
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).
|
||||
|
||||
As an other example, once again assuming the availability of a
|
||||
tt(Person(char const *name)) constructor, consider:
|
||||
verb(
|
||||
Person namedPerson()
|
||||
{
|
||||
string name;
|
||||
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.
|
||||
|
||||
cin >> name;
|
||||
return name.c_str();
|
||||
}
|
||||
)
|
||||
Here, even though the return value tt(name.c_str()) doesn't match the
|
||||
return type tt(Person), there is a em(constructor) available to construct a
|
||||
tt(Person) from a tt(char const *). Since such a constructor is available, the
|
||||
(anonymous) return value can be constructed by em(promoting) a
|
||||
hi(promoting a type) tt(char const *) type to a tt(Person) type using an
|
||||
appropriate constructor.
|
||||
|
||||
>>>>>>>>>)
|
||||
|
||||
|
||||
Conversions are performed not only by conversion operators, but also by
|
||||
i(constructors having one parameter) (or multiple parameters, having
|
||||
i(default argument values) beyond the first parameter).
|
||||
|
||||
Consider the class tt(Person) introduced in chapter ref(MEMORY). This
|
||||
class has a constructor
|
||||
centt(Person(char const *name, char const *address, char const *phone))
|
||||
This constructor could be given default argument values:
|
||||
verb(
|
||||
Person(char const *name, char const *address = "<unknown>",
|
||||
char const *phone = "<unknown>");
|
||||
)
|
||||
In several situations this constructor might be used intentionally,
|
||||
possibly providing the default tt(<unknown>) texts for the address and phone
|
||||
numbers. For example:
|
||||
verb(
|
||||
Person frank("Frank", "Room 113", "050 363 9281");
|
||||
)
|
||||
Also, functions might use tt(Person) objects as parameters, e.g., the
|
||||
following member in a fictitious class tt(PersonData) could be available:
|
||||
verb(
|
||||
PersonData &PersonData::operator+=(Person const &person);
|
||||
)
|
||||
Now, combining the above two pieces of code, we might, do something like
|
||||
verb(
|
||||
PersonData dbase;
|
||||
|
||||
dbase += frank; // add frank to the database
|
||||
)
|
||||
So far, so good. However, since the tt(Person) constructor can also be
|
||||
used as a conversion operator, it is em(also) possible to do:
|
||||
verb(
|
||||
dbase += "karel";
|
||||
)
|
||||
Here, the tt(char const *) text `tt(karel)' is converted to an (anonymous)
|
||||
tt(Person) object using the abovementioned tt(Person) constructor: the second
|
||||
and third parameters use their i(default value)s. Here, an
|
||||
emi(implicit conversion) is performed from a tt(char const *) to a
|
||||
tt(Person) object, which might not be what the programmer had in mind when the
|
||||
class tt(Person) was constructed.
|
||||
|
||||
As another example, consider the situation where a class representing a
|
||||
container is constructed. Let's assume that the initial construction of
|
||||
objects of this class is rather complex and time-consuming, but em(expanding)
|
||||
an object so that it can accomodate more elements is even more
|
||||
time-consuming. Such a situation might arise when a hash-table is initially
|
||||
constructed to contain tt(n) elements: that's OK as long as the table is not
|
||||
full, but when the table must be expanded, all its elements normally must be
|
||||
rehashed to allow for the new table size.
|
||||
|
||||
Such a class could (partially) be defined as follows:
|
||||
verb(
|
||||
class HashTable
|
||||
{
|
||||
size_t d_maxsize;
|
||||
|
||||
public:
|
||||
HashTable(size_t n); // n: initial table size
|
||||
size_t size(); // returns current # of elements
|
||||
|
||||
// add new key and value
|
||||
void add(std::string const &key, std::string const &value);
|
||||
};
|
||||
)
|
||||
Now consider the following implementation of tt(add()):
|
||||
verb(
|
||||
void HashTable::add(string const &key, string const &value)
|
||||
{
|
||||
if (size() > d_maxsize * 0.75) // table gets rather full
|
||||
*this = size() * 2; // Oops: not what we want!
|
||||
|
||||
// etc.
|
||||
}
|
||||
)
|
||||
In the first line of the body of tt(add()) the programmer first determines
|
||||
how full the hashtable currently is: if it's more than three quarter full,
|
||||
then the intention is to double the size of the hashtable. Although this
|
||||
succeeds, the hashtable will completely fail to fulfill its purpose:
|
||||
accidentally the programmer assigns an size_t value, intending to tell the
|
||||
hashtable what its new size should be. This results in the following unwelcome
|
||||
surprise:
|
||||
In addition to the copy constructor tt(DataBase) offers a default
|
||||
constructor and several additional constructors:
|
||||
itemization(
|
||||
it() The compiler notices that no tt(operator=(size_t newsize)) is
|
||||
available for tt(HashTable).
|
||||
it() There is, however, a constructor accepting an size_t, em(and) the
|
||||
default overloaded assignment operator is still available, expecting a
|
||||
tt(HashTable) as its right-hand operand.
|
||||
it() Thus, the rvalue of the assignment (a tt(HashTable)) is obtained by
|
||||
(implicitly) constructing an (empty) tt(HashTable) that can accomodate
|
||||
tt(size() * 2) elements.
|
||||
it() The just constructed empty tt(HashTable) is thereupon assigned to the
|
||||
current tt(HashTable), thus em(removing all hitherto stored elements from the
|
||||
current tt(HashTable)).
|
||||
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.
|
||||
)
|
||||
If an hi(constructor: implicit use) implicit use of a constructor is not
|
||||
appropriate (or dangerous), it can be prevented using the ti(explicit)
|
||||
modifier with the constructor. Constructors using the tt(explicit) modifier
|
||||
can only be used for the i(explicit construction) of objects, and cannot be
|
||||
used as implicit type convertors anymore. For example, to prevent the implicit
|
||||
conversion from tt(size_t) to tt(HashTable) the class interface of the
|
||||
class tt(HashTable) should declare the constructor
|
||||
|
||||
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(
|
||||
explicit HashTable(size_t n);
|
||||
DataBase db;
|
||||
DataBase db2;
|
||||
Person person;
|
||||
|
||||
db2 = db; // 1
|
||||
db2 = person; // 2
|
||||
db2 = 10; // 3
|
||||
db2 = cin; // 4
|
||||
)
|
||||
Now the compiler will catch the error in the compilation of
|
||||
tt(HashTable::add()), producing an error message like
|
||||
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
|
||||
will wait for the data of 10 persons to appear at the standard input
|
||||
stream. Nothing like that is suggested by tt(db2 = 10).
|
||||
|
||||
All four statements are the result of implicit
|
||||
i(promotion)s. Since constructors accepting, respectively a tt(Person), a
|
||||
tt(size_t) and an tt(istream) have been defined for tt(PersonData) and since
|
||||
the assignment operator expects a tt(PersonData) right-hand side (rhs)
|
||||
argument the compiler will first convert 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 1-4 would not have compiled if the constructors
|
||||
expecting one argument would have been declared using tt(explicit). E.g.,
|
||||
verb(
|
||||
error: no match for 'operator=' in
|
||||
'*this = (this->HashTable::size()() * 2)'
|
||||
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).
|
||||
|
|
Loading…
Reference in a new issue