Previous | Table of Contents | Next |
For this example, we are arbitrarily saying that any Vehicle should be considered heavy if its weight exceeds 12,000 of whatever units we use to express weight.
Because of the conversion behavior weve seen so far, heavy accepts an argument of class Vehicle or any class derived from it. This accepting behavior is convenient. What might be less convenient is that the heavy function does not necessarily behave as intended.
For example, it is tempting to assume that a1 > 12000 always yields the same result as heavy(a1). Thats why we wrote heavy, after all. Yet as it stands, that equivalence is not guaranteed. Think about why before reading on.
The reason is that the parameter v in function heavy is just a Vehicle and not a Vehicle&. Its an object, not a pointer or reference to an object. In the call heavy(a1), the parameter v is created as a copy of the Vehicle part of the argument a1, which means that v.weight() calls Vehicle::weight. What else could it call? By the time we reach the point of calling v.weight(), there is nothing to distinguish v from any other Vehicle object. It is certainly not an Automobile.
On the other hand, a1.weight() will, of course, call Automobile::weight. In other words, the effect of calling heavy(a1) is to copy a1 into a Vehicle object and then use Vehicle::weight to determine its weight.
Converting a1 to a Vehicle and then calling the weight() member of the converted object might give the same answer as calling Automobile::weight directly, of course, or it might not. In general, though, there is no reason to suspect that the answer would be the sameelse why bother defining Automobile::weight at all?
If you want a1.heavy() to mean the same thing as a1.weight() > 12000, heres how to do it:
int heavy(const Vehicle& v) { return v.weight() > 12000; }
Because v is now a reference to a Vehicle instead of a separate Vehicle object, no copies are made. That means that v.weight() is a virtual call, so calling heavy(a1) results in calling Automobile::weight, as intended.
8.3.4.1. Base Class Summary
Some classes are defined to make it easy to use their objects as values. That is, it is easy to copy objects of those classes, and there is no particular distinction between one object and another, aside from its address. Other classes are intended to have their objects used as objects. Copying an object of such a class may be difficult, or even meaningless, and the identity of each individual object becomes important.
The distinction between objects and values becomes particularly important in programs that pass objects as function arguments: Does the function take an object or a value? Giving an object to a function that expects a value is particularly troublesome.
This trouble can be compounded when the class of the object says nothing about how to copy objects of that class. In such cases, the compiler assumes that the objects are used as values and defines copying in terms of copying what it thinks are the relevant components of the objects. The compilers guesswork is often right when the class is intended to be used as a value. In other cases, the class author must think about how the class objects are used and define (or prohibit) copying appropriately.
The distinction between objects and values can be particularly important in the context of container classes. The standard library containers are defined to contain values, rather than objects. That is, the containers store copies of the objects that are given to them, rather than the objects themselves. This distinction can be important when defining classes whose objects are intended to be placed in containers.
In addition, some container classes place additional requirements on their components. For example, the standard library set class requires that its components be of a type for which < is an order relation. Defining these operations appropriately is not always as easy as it looks.
8.3.5.1. Stating Requirements on Containers
Unlike some other languages that support generics, C++ has no way of saying, as part of a template definition, what properties the types must have that are used to instantiate the template. As an example, consider the standard library template set. If T is a type, then among the requirements that the set template places on type T is that objects of type T must be comparable. That is, if t1 and t2 are objects of type T, then t1<t2 must be defined to be an appropriate order relation.
Why doesnt the C++ language have some way of saying explicitly that T must have < defined? Why leave it up to the documentation? There are at least three reasons:
We will consider each of these reasons in turn.
Previous | Table of Contents | Next |