Archive-name: C++-faq/part7
Posting-Frequency: monthly
Last-modified: Feb 29, 2000
URL: http://marshall-cline.home.att.net/cpp-faq-lite/
AUTHOR: Marshall Cline / [email protected] / 972-931-9470
COPYRIGHT: This posting is part of "C++ FAQ Lite." The entire "C++ FAQ Lite"
document is Copyright(C)1991-2000 Marshall Cline, Ph.D., [email protected].
All rights reserved. Copying is permitted only under designated situations.
For details, see section [1].
NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS. THE AUTHOR PROVIDES NO
WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
PURPOSE.
C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
the C++ FAQ Book. The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is 500%
larger than this document, and is available in bookstores. For details, see
section [3].
==============================================================================
SECTION [18]: Const correctness
[18.1] What is "const correctness"?
A good thing. It means using the keyword const to prevent const objects from
getting mutated.
For example, if you wanted to create a function f() that accepted a String,
plus you want to promise callers not to change the caller's String that gets
passed to f(), you can have f() receive its String parameter...
* void f1(const String& s); // Pass by reference-to-const
* void f2(const String* sptr); // Pass by pointer-to-const
* void f3(String s); // Pass by value
In the pass by reference-to-const and pass by pointer-to-const cases, any
attempts to change to the caller's String within the f() functions would be
flagged by the compiler as an error at compile-time. This check is done
entirely at compile-time: there is no run-time space or speed cost for the
const. In the pass by value case (f3()), the called function gets a copy of
the caller's String. This means that f3() can change its local copy, but the
copy is destroyed when f3() returns. In particular f3() cannot change the
caller's String object.
As an opposite example, if you wanted to create a function g() that accepted a
String, but you want to let callers know that g() might change the caller's
String object. In this case you can have g() receive its String parameter...
* void g1(String& s); // Pass by reference-to-non-const
* void g2(String* sptr); // Pass by pointer-to-non-const
The lack of const in these functions tells the compiler that they are allowed
to (but are not required to) change the caller's String object. Thus they can
pass their String to any of the f() functions, but only f3() (the one that
receives its parameter "by value") can pass its String to g1() or g2(). If
f1() or f2() need to call either g() function, a local copy of the String
object must be passed to the g() function; the parameter to f1() or f2() cannot
be directly passed to either g() function. E.g.,
void g1(String& s);
void f1(const String& s)
{
g1(s); // Compile-time Error since s is const
String localCopy = s;
g1(localCopy); // OK since localCopy is not const
}
Naturally in the above case, any changes that g1() makes are made to the
localCopy object that is local to f1(). In particular, no changes will be made
to the const parameter that was passed by reference to f1().
==============================================================================
[18.2] How is "const correctness" related to ordinary type safety?
Declaring the const-ness of a parameter is just another form of type safety.
It is almost as if a const String, for example, is a different class than an
ordinary String, since the const variant is missing the various mutative
operations in the non-const variant (e.g., you can imagine that a const String
simply doesn't have an assignment operator).
If you find ordinary type safety helps you get systems correct (it does;
especially in large systems), you'll find const correctness helps also.
==============================================================================
[18.3] Should I try to get things const correct "sooner" or "later"?
At the very, very, very beginning.
Back-patching const correctness results in a snowball effect: every const you
add "over here" requires four more to be added "over there."
==============================================================================
[18.4] What does "const Fred* p" mean?
It means p points to an object of class Fred, but p can't be used to change
that Fred object (naturally p could also be NULL).
For example, if class Fred has a const member function[18.9] called inspect(),
saying p->inspect() is OK. But if class Fred has a non-const member
function[18.9] called mutate(), saying p->mutate() is an error (the error is
caught by the compiler; no run-time tests are done, which means const doesn't
slow your program down).
==============================================================================
[18.5] What's the difference between "const Fred* p", "Fred* const p" and
"const Fred* const p"?
You have to read pointer declarations right-to-left.
* const Fred* p means "p points to a Fred that is const" -- that is, the Fred
object can't be changed via p[18.13].
* Fred* const p means "p is a const pointer to a Fred" -- that is, you can
change the Fred object via p[18.13], but you can't change the pointer p
itself.
* const Fred* const p means "p is a const pointer to a const Fred" -- that is,
you can't change the pointer p itself, nor can you change the Fred object
via p[18.13].
==============================================================================
[18.6] What does "const Fred& x" mean?
It means x aliases a Fred object, but x can't be used to change that Fred
object.
For example, if class Fred has a const member function[18.9] called inspect(),
saying x.inspect() is OK. But if class Fred has a non-const member
function[18.9] called mutate(), saying x.mutate() is an error (the error is
caught by the compiler; no run-time tests are done, which means const doesn't
slow your program down).
==============================================================================
[18.7] Does "Fred& const x" make any sense?
No, it is nonsense.
To find out what the above declaration means, you have to read it
right-to-left[18.5]. Thus "Fred& const x" means "x is a const reference to a
Fred". But that is redundant, since references are always const. You can't
reseat a reference[8.4]. Never. With or without the const.
In other words, "Fred& const x" is functionally equivalent to "Fred& x". Since
you're gaining nothing by adding the const after the &, you shouldn't add it
since it will confuse people. I.e., the const will make some people think that
the Fred is const, as if you had said "const Fred& x".
==============================================================================
[18.8] What does "Fred const& x" mean?
"Fred const& x" is functionally equivalent to "const Fred& x"[18.6].
The problem with using "Fred const& x" (with the const before the &) is that it
could easily be mis-typed as the nonsensical "Fred &const x"[18.7] (with the
const after the &).
Better to simply use const Fred& x.
==============================================================================
[18.9] What is a "const member function"?
A member function that inspects (rather than mutates) its object.
A const member function is indicated by a const suffix just after the member
function's parameter list. Member functions with a const suffix are called
"const member functions" or "inspectors." Member functions without a const
suffix are called "non-const member functions" or "mutators."
class Fred {
public:
void inspect() const; // This member promises NOT to change *this
void mutate(); // This member function might change *this
};
void userCode(Fred& changeable, const Fred& unchangeable)
{
changeable.inspect(); // OK: doesn't change a changeable object
changeable.mutate(); // OK: changes a changeable object
unchangeable.inspect(); // OK: doesn't change an unchangeable object
unchangeable.mutate(); // ERROR: attempt to change unchangeable object
}
The error in unchangeable.mutate() is caught at compile time. There is no
runtime space or speed penalty for const.
The trailing const on inspect() member function means that the abstract
(client-visible) state of the object isn't going to change. This is slightly
different from promising that the "raw bits" of the object's struct aren't
going to change. C++ compilers aren't allowed to take the "bitwise"
interpretation unless they can solve the aliasing problem, which normally can't
be solved (i.e., a non-const alias could exist which could modify the state of
the object). Another (important) insight from this aliasing issue: pointing at
an object with a pointer-to-const doesn't guarantee that the object won't
change; it promises only that the object won't change via that pointer.
==============================================================================
[18.10] What do I do if I want to update an "invisible" data member inside a
const member function? [UPDATED!]
[Recently added a warning against use of const_cast on const objects thanks to
TiTi (on 3/00).]
Use mutable (or, as a last resort, use const_cast).
A small percentage of inspectors need to make innocuous changes to data members
(e.g., a Set object might want to cache its last lookup in hopes of improving
the performance of its next lookup). By saying the changes are "innocuous," I
mean that the changes wouldn't be visible from outside the object's interface
(otherwise the member function would be a mutator rather than an inspector).
When this happens, the data member which will be modified should be marked as
mutable (put the mutable keyword just before the data member's declaration;
i.e., in the same place where you could put const). This tells the compiler
that the data member is allowed to change during a const member function. If
your compiler doesn't support the mutable keyword, you can cast away the
const'ness of this via the const_cast keyword (but see the NOTE below before
doing this). E.g., in Set::lookup() const, you might say,
Set* self = const_cast<Set*>(this);
// See the NOTE below before doing this!
After this line, self will have the same bits as this (e.g., self == this), but
self is a Set* rather than a const Set*. Therefore you can use self to modify
the object pointed to by this.
NOTE: there is an extremely unlikely error that can occur with const_cast. It
only happens when three very rare things are combined at the same time: a data
member that ought to be mutable (such as is discussed above), a compiler that
doesn't support the mutable keyword, and an object that was originally defined
to be const (as opposed to a normal, non-const object that is pointed to by a
pointer-to-const). Although this combination is so rare that it may never
happen to you, if it ever did happen the code may not work (the Standard says
the behavior is undefined).
If you ever want to use const_cast, use mutable instead. In other words, if
you ever need to change a member of an object, and that object is pointed to by
a pointer-to-const, the safest and simplest thing to do is add mutable to the
member's declaration. You can use const_cast if you are sure that the actual
object isn't const (e.g., if you are sure the object is declared something like
this: Set s;), but if the object itself might be const (e.g., if it might be
declared like: const Set s;), use mutable rather than const_cast.
Please don't write and tell me that version X of compiler Y on machine Z allows
you to change a non-mutable member of a const object. I don't care -- it is
illegal according to the language and your code will probably fail on a
different compiler or even a different version (an upgrade) of the same
compiler. Just say no. Use mutable instead.
==============================================================================
[18.11] Does const_cast mean lost optimization opportunities?
In theory, yes; in practice, no.
Even if the language outlawed const_cast, the only way to avoid flushing the
register cache across a const member function call would be to solve the
aliasing problem (i.e., to prove that there are no non-const pointers that
point to the object). This can happen only in rare cases (when the object is
constructed in the scope of the const member function invocation, and when all
the non-const member function invocations between the object's construction and
the const member function invocation are statically bound, and when every one
of these invocations is also inlined, and when the constructor itself is
inlined, and when any member functions the constructor calls are inline).
==============================================================================
[18.12] Why does the compiler allow me to change an int after I've pointed at
it with a const int*?
Because "const int* p" means "p promises not to change the *p," not "*p
promises not to change."
Causing a const int* to point to an int doesn't const-ify the int. The int
can't be changed via the const int*, but if someone else has an int* (note: no
const) that points to ("aliases") the same int, then that int* can be used to
change the int. For example:
void f(const int* p1, int* p2)
{
int i = *p1; // Get the (original) value of *p1
*p2 = 7; // If p1 == p2, this will also change *p1
int j = *p1; // Get the (possibly new) value of *p1
if (i != j) {
cout << "*p1 changed, but it didn't change via pointer p1!\n";
assert(p1 == p2); // This is the only way *p1 could be different
}
}
int main()
{
int x;
f(&x, &x); // This is perfectly legal (and even moral!)
}
Note that main() and f(const int*,int*) could be in different compilation units
that are compiled on different days of the week. In that case there is no way
the compiler can possibly detect the aliasing at compile time. Therefore there
is no way we could make a language rule that prohibits this sort of thing. In
fact, we wouldn't even want to make such a rule, since in general it's
considered a feature that you can have many pointers pointing to the same
thing. The fact that one of those pointers promises not to change the
underlying "thing" is just a promise made by the pointer; it's not a promise
made by the "thing".
==============================================================================
[18.13] Does "const Fred* p" mean that *p can't change? [UPDATED!]
[Recently added an indication that there might be other non-const ways to get
at the object thanks to Stan Brown (on 1/00).]
No! (This is related to the FAQ about aliasing of int pointers[18.12].)
"const Fred* p" means that the Fred can't be changed via pointer p, but there
might be other ways to get at the object without going through a const (such as
an aliased non-const pointer such as a Fred*). For example, if you have two
pointers "const Fred* p" and "Fred* q" that point to the same Fred object
(aliasing), pointer q can be used to change the Fred object but pointer p
cannot.
class Fred {
public:
void inspect() const; // A const member function[18.9]
void mutate(); // A non-const member function[18.9]
};
int main()
{
Fred f;
const Fred* p = &f;
Fred* q = &f;
p->inspect(); // OK: No change to *p
p->mutate(); // Error: Can't change *p via p
q->inspect(); // OK: q is allowed to inspect the object
q->mutate(); // OK: q is allowed to mutate the object
f.inspect(); // OK: f is allowed to inspect the object
f.mutate(); // OK: f is allowed to mutate the object
}
==============================================================================
SECTION [19]: Inheritance -- basics
[19.1] Is inheritance important to C++?
Yep.
Inheritance is what separates abstract data type (ADT) programming from OO
programming.
==============================================================================
[19.2] When would I use inheritance?
As a specification device.
Human beings abstract things on two dimensions: part-of and kind-of. A Ford
Taurus is-a-kind-of-a Car, and a Ford Taurus has-a Engine, Tires, etc. The
part-of hierarchy has been a part of software since the ADT style became
relevant; inheritance adds "the other" major dimension of decomposition.
==============================================================================
[19.3] How do you express inheritance in C++?
By the : public syntax:
class Car : public Vehicle {
public:
// ...
};
We state the above relationship in several ways:
* Car is "a kind of a" Vehicle
* Car is "derived from" Vehicle
* Car is "a specialized" Vehicle
* Car is the "subclass" of Vehicle
* Vehicle is the "base class" of Car
* Vehicle is the "superclass" of Car (this not as common in the C++ community)
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[19.4] Is it OK to convert a pointer from a derived class to its base class?
Yes.
An object of a derived class is a kind of the base class. Therefore the
conversion from a derived class pointer to a base class pointer is perfectly
safe, and happens all the time. For example, if I am pointing at a car, I am
in fact pointing at a vehicle, so converting a Car* to a Vehicle* is perfectly
safe and normal:
void f(Vehicle* v);
void g(Car* c) { f(c); } // Perfectly safe; no cast
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[19.5] What's the difference between public:, private:, and protected:?
* A member (either data member or member function) declared in a private:
section of a class can only be accessed by member functions and friends[14]
of that class
* A member (either data member or member function) declared in a protected:
section of a class can only be accessed by member functions and friends[14]
of that class, and by member functions and friends[14] of derived classes
* A member (either data member or member function) declared in a public:
section of a class can be accessed by anyone
==============================================================================
[19.6] Why can't my derived class access private: things from my base class?
To protect you from future changes to the base class.
Derived classes do not get access to private members of a base class. This
effectively "seals off" the derived class from any changes made to the private
members of the base class.
==============================================================================
[19.7] How can I protect subclasses from breaking when I change internal parts?
A class has two distinct interfaces for two distinct sets of clients:
* It has a public: interface that serves unrelated classes
* It has a protected: interface that serves derived classes
Unless you expect all your subclasses to be built by your own team, you should
consider making your base class's bits be private:, and use protected: inline
access functions by which derived classes will access the private data in the
base class. This way the private bits can change, but the derived class's code
won't break unless you change the protected access functions.
==============================================================================
SECTION [20]: Inheritance -- virtual functions
[20.1] What is a "virtual member function"?
From an OO perspective, it is the single most important feature of C++: [6.8],
[6.9].
A virtual function allows derived classes to replace the implementation
provided by the base class. The compiler makes sure the replacement is always
called whenever the object in question is actually of the derived class, even
if the object is accessed by a base pointer rather than a derived pointer.
This allows algorithms in the base class to be replaced in the derived class,
even if users don't know about the derived class.
The derived class can either fully replace ("override") the base class member
function, or the derived class can partially replace ("augment") the base class
member function. The latter is accomplished by having the derived class member
function call the base class member function, if desired.
==============================================================================
[20.2] How can C++ achieve dynamic binding yet also static typing? [UPDATED!]
[Recently added the definition of polymorphism thanks to Kemberli Jennings (on
1/00).]
When you have a pointer to an object, the object may actually be of a class
that is derived from the class of the pointer (e.g., a Vehicle* that is
actually pointing to a Car object; this is called "polymorphism"). Thus there
are two types: the (static) type of the pointer (Vehicle, in this case), and
the (dynamic) type of the pointed-to object (Car, in this case).
Static typing means that the legality of a member function invocation is
checked at the earliest possible moment: by the compiler at compile time. The
compiler uses the static type of the pointer to determine whether the member
function invocation is legal. If the type of the pointer can handle the member
function, certainly the pointed-to object can handle it as well. E.g., if
Vehicle has a certain member function, certainly Car also has that member
function since Car is a kind-of Vehicle.
Dynamic binding means that the address of the code in a member function
invocation is determined at the last possible moment: based on the dynamic type
of the object at run time. It is called "dynamic binding" because the binding
to the code that actually gets called is accomplished dynamically (at run
time). Dynamic binding is a result of virtual functions.
==============================================================================
[20.3] What's the difference between how virtual and non-virtual member
functions are called?
Non-virtual member functions are resolved statically. That is, the member
function is selected statically (at compile-time) based on the type of the
pointer (or reference) to the object.
In contrast, virtual member functions are resolved dynamically (at run-time).
That is, the member function is selected dynamically (at run-time) based on the
type of the object, not the type of the pointer/reference to that object. This
is called "dynamic binding." Most compilers use some variant of the following
technique: if the object has one or more virtual functions, the compiler puts a
hidden pointer in the object called a "virtual-pointer" or "v-pointer." This
v-pointer points to a global table called the "virtual-table" or "v-table."
The compiler creates a v-table for each class that has at least one virtual
function. For example, if class Circle has virtual functions for draw() and
move() and resize(), there would be exactly one v-table associated with class
Circle, even if there were a gazillion Circle objects, and the v-pointer of
each of those Circle objects would point to the Circle v-table. The v-table
itself has pointers to each of the virtual functions in the class. For
example, the Circle v-table would have three pointers: a pointer to
Circle::draw(), a pointer to Circle::move(), and a pointer to Circle::resize().
During a dispatch of a virtual function, the run-time system follows the
object's v-pointer to the class's v-table, then follows the appropriate slot in
the v-table to the method code.
The space-cost overhead of the above technique is nominal: an extra pointer per
object (but only for objects that will need to do dynamic binding), plus an
extra pointer per method (but only for virtual methods). The time-cost
overhead is also fairly nominal: compared to a normal function call, a virtual
function call requires two extra fetches (one to get the value of the
v-pointer, a second to get the address of the method). None of this runtime
activity happens with non-virtual functions, since the compiler resolves
non-virtual functions exclusively at compile-time based on the type of the
pointer.
Note: the above discussion is simplified considerably, since it doesn't account
for extra structural things like multiple inheritance, virtual inheritance,
RTTI, etc., nor does it account for space/speed issues such as page faults,
calling a function via a pointer-to-function, etc. If you want to know about
those other things, please ask comp.lang.c++; PLEASE DO NOT SEND E-MAIL TO ME!
==============================================================================
[20.4] When should my destructor be virtual?
When you may delete a derived object via a base pointer.
virtual functions bind to the code associated with the class of the object,
rather than with the class of the pointer/reference. When you say
delete basePtr, and the base class has a virtual destructor, the destructor
that gets invoked is the one associated with the type of the object *basePtr,
rather than the one associated with the type of the pointer. This is generally
A Good Thing.
TECHNO-GEEK WARNING; PUT YOUR PROPELLER HAT ON.
Technically speaking, you need a base class's destructor to be virtual if and
only if you intend to allow someone to invoke an object's destructor via a base
class pointer (this is normally done implicitly via delete), and the object
being destructed is of a derived class that has a non-trivial destructor. A
class has a non-trivial destructor if it either has an explicitly defined
destructor, or if it has a member object or a base class that has a non-trivial
destructor (note that this is a recursive definition (e.g., a class has a
non-trivial destructor if it has a member object (which has a base class (which
has a member object (which has a base class (which has an explicitly defined
destructor)))))).
END TECHNO-GEEK WARNING; REMOVE YOUR PROPELLER HAT
If you had a hard grokking the previous rule, try this (over)simplified one on
for size: A class should have a virtual destructor unless that class has no
virtual functions. Rationale: if you have any virtual functions at all, you're
probably going to be doing "stuff" to derived objects via a base pointer, and
some of the "stuff" you may do may include invoking a destructor (normally done
implicitly via delete). Plus once you've put the first virtual function into a
class, you've already paid all the per-object space cost that you'll ever pay
(one pointer per object; note that this is theoretically compiler-specific; in
practice everyone does it pretty much the same way), so making the destructor
virtual won't generally cost you anything extra.
==============================================================================
[20.5] What is a "virtual constructor"? [UPDATED!]
[Recently added the paragraph on Covariant Return Types (on 3/00).]
An idiom that allows you to do something that C++ doesn't directly support.
You can get the effect of a virtual constructor by a virtual clone() member
function (for copy constructing), or a virtual create() member function (for
the default constructor[10.4]).
class Shape {
public:
virtual ~Shape() { } // A virtual destructor[20.4]
virtual void draw() = 0; // A pure virtual function[22.4]
virtual void move() = 0;
// ...
virtual Shape* clone() const = 0; // Uses the copy constructor
virtual Shape* create() const = 0; // Uses the default constructor[10.4]
};
class Circle : public Shape {
public:
Circle* clone() const { return new Circle(*this); }
Circle* create() const { return new Circle(); }
// ...
};
In the clone() member function, the new Circle(*this) code calls Circle's copy
constructor to copy the state of this into the newly created Circle object. In
the create() member function, the new Circle() code calls Circle's default
constructor[10.4].
Users use these as if they were "virtual constructors":
void userCode(Shape& s)
{
Shape* s2 = s.clone();
Shape* s3 = s.create();
// ...
delete s2; // You probably need a virtual destructor[20.4] here
delete s3;
}
This function will work correctly regardless of whether the Shape is a Circle,
Square, or some other kind-of Shape that doesn't even exist yet.
Note: The return type of Circle's clone() member function is intentionally
different from the return type of Shape's clone() member function. This is
called Covariant Return Types, a feature that was not originally part of the
language. If your compiler complains at the declaration of
Circle* clone() const within class Circle (e.g., saying "The return type is
different" or "The member function's type differs from the base class virtual
function by return type alone"), you have an old compiler and you'll have to
change the return type to Shape*.
==============================================================================
SECTION [21]: Inheritance -- proper inheritance and substitutability
[21.1] Should I hide member functions that were public in my base class?
Never, never, never do this. Never. Never!
Attempting to hide (eliminate, revoke, privatize) inherited public member
functions is an all-too-common design error. It usually stems from muddy
thinking.
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[21.2] Derived* --> Base* works OK; why doesn't Derived** --> Base** work?
C++ allows a Derived* to be converted to a Base*, since a Derived object is a
kind of a Base object. However trying to convert a Derived** to a Base** is
flagged as an error. Although this error may not be obvious, it is nonetheless
a good thing. For example, if you could convert a Car** to a Vehicle**, and if
you could similarly convert a NuclearSubmarine** to a Vehicle**, you could
assign those two pointers and end up making a Car* point at a NuclearSubmarine:
class Vehicle {
public:
virtual ~Vehicle() { }
virtual void startEngine() = 0;
};
class Car : public Vehicle {
public:
virtual void startEngine();
virtual void openGasCap();
};
class NuclearSubmarine : public Vehicle {
public:
virtual void startEngine();
virtual void fireNuclearMissle();
};
int main()
{
Car car;
Car* carPtr = &car;
Car** carPtrPtr = &carPtr;
Vehicle** vehiclePtrPtr = carPtrPtr; // This is an error in C++
NuclearSubmarine sub;
NuclearSubmarine* subPtr = ⊂
*vehiclePtrPtr = subPtr;
// This last line would have caused carPtr to point to sub !
carPtr->openGasCap(); // This might call fireNuclearMissle()!
}
In other words, if it was legal to convert a Derived** to a Base**, the Base**
could be dereferenced (yielding a Base*), and the Base* could be made to point
to an object of a different derived class, which could cause serious problems
for national security (who knows what would happen if you invoked the
openGasCap() member function on what you thought was a Car, but in reality it
was a NuclearSubmarine!! Try the above code out and see what it does -- on most
compilers it will call NuclearSubmarine::fireNuclearMissle()!
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[21.3] Is a parking-lot-of-Car a kind-of parking-lot-of-Vehicle?
Nope.
I know it sounds strange, but it's true. You can think of this as a direct
consequence of the previous FAQ, or you can reason it this way: if the kind-of
relationship were valid, then someone could point a parking-lot-of-Vehicle
pointer at a parking-lot-of-Car. But parking-lot-of-Vehicle has a
addNewVehicleToParkingLot(Vehicle&) member function which can add any Vehicle
object to the parking lot. This would allow you to park a NuclearSubmarine in
a parking-lot-of-Car. Certainly it would be surprising if someone removed what
they thought was a Car from the parking-lot-of-Car, only to find that it is
actually a NuclearSubmarine.
Another way to say this truth: a container of Thing is not a kind-of container
of Anything even if a Thing is a kind-of an Anything. Swallow hard; it's true.
You don't have to like it. But you do have to accept it.
One last example which we use in our OO/C++ training courses: "A Bag-of-Apple
is not a kind-of Bag-of-Fruit." If a Bag-of-Apple could be passed as a
Bag-of-Fruit, someone could put a Banana into the Bag, even though it is
supposed to only contain Apples!
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[21.4] Is an array of Derived a kind-of array of Base?
Nope.
This is a corollary of the previous FAQ. Unfortunately this one can get you
into a lot of hot water. Consider this:
class Base {
public:
virtual void f(); // 1
};
class Derived : public Base {
public:
// ...
private:
int i_; // 2
};
void userCode(Base* arrayOfBase)
{
arrayOfBase[1].f(); // 3
}
int main()
{
Derived arrayOfDerived[10]; // 4
userCode(arrayOfDerived); // 5
}
The compiler thinks this is perfectly type-safe. Line 5 converts a Derived* to
a Base*. But in reality it is horrendously evil: since Derived is larger than
Base, the pointer arithmetic done on line 3 is incorrect: the compiler uses
sizeof(Base) when computing the address for arrayOfBase[1], yet the array is an
array of Derived, which means the address computed on line 3 (and the
subsequent invocation of member function f()) isn't even at the beginning of
any object! It's smack in the middle of a Derived object. Assuming your
compiler uses the usual approach to virtual[20] functions, this will
reinterpret the int i_ of the first Derived as if it pointed to a virtual
table, it will follow that "pointer" (which at this point means we're digging
stuff out of a random memory location), and grab one of the first few words of
memory at that location and interpret them as if they were the address of a C++
member function, then load that (random memory location) into the instruction
pointer and begin grabbing machine instructions from that memory location. The
chances of this crashing are very high.
The root problem is that C++ can't distinguish between a pointer-to-a-thing and
a pointer-to-an-array-of-things. Naturally C++ "inherited" this feature from
C.
NOTE: If we had used an array-like class (e.g., vector<Derived> from STL[32.1])
instead of using a raw array, this problem would have been properly trapped as
an error at compile time rather than a run-time disaster.
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[21.5] Does array-of-Derived is-not-a-kind-of array-of-Base mean arrays are
bad?
Yes, arrays are evil. (only half kidding).
Seriously, arrays are very closely related to pointers, and pointers are
notoriously difficult to deal with. But if you have a complete grasp of why
the above few FAQs were a problem from a design perspective (e.g., if you
really know why a container of Thing is not a kind-of container of Anything),
and if you think everyone else who will be maintaining your code also has a
full grasp on these OO design truths, then you should feel free to use arrays.
But if you're like most people, you should use a template container class such
as vector<T> from STL[32.1] rather than raw arrays.
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
[21.6] Is a Circle a kind-of an Ellipse?
Not if Ellipse promises to be able to change its size asymmetrically.
For example, suppose Ellipse has a setSize(x,y) member function, and suppose
this member function promises the Ellipse's width() will be x, and its height()
will be y. In this case, Circle can't be a kind-of Ellipse. Simply put, if
Ellipse can do something Circle can't, then Circle can't be a kind of Ellipse.
This leaves two potential (valid) relationships between Circle and Ellipse:
* Make Circle and Ellipse completely unrelated classes
* Derive Circle and Ellipse from a base class representing "Ellipses that
can't necessarily perform an unequal-setSize() operation"
In the first case, Ellipse could be derived from class AsymmetricShape, and
setSize(x,y) could be introduced in AsymmetricShape. However Circle could be
derived from SymmetricShape which has a setSize(size) member function.
In the second case, class Oval could only have setSize(size) which sets both
the width() and the height() to size. Ellipse and Circle could both inherit
from Oval. Ellipse --but not Circle-- could add the setSize(x,y) operation
(but beware of the hiding rule[23.3] if the same member function name setSize()
is used for both operations).
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
(Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to
prevent users from changing the dimensions of an Ellipse, in which case it
would be a valid design choice to not have a setSize(x,y) method in Ellipse.
However this series of FAQs discusses what to do when you want to create a
derived class of a pre-existing base class that has an "unacceptable" method in
it. Of course the ideal situation is to discover this problem when the base
class doesn't yet exist. But life isn't always ideal...)
==============================================================================
[21.7] Are there other options to the "Circle is/isnot kind-of Ellipse"
dilemma?
If you claim that all Ellipses can be squashed asymmetrically, and you claim
that Circle is a kind-of Ellipse, and you claim that Circle can't be squashed
asymmetrically, clearly you've got to adjust (revoke, actually) one of your
claims. Thus you've either got to get rid of Ellipse::setSize(x,y), get rid of
the inheritance relationship between Circle and Ellipse, or admit that your
Circles aren't necessarily circular.
Here are the two most common traps new OO/C++ programmers regularly fall into.
They attempt to use coding hacks to cover up a broken design (they redefine
Circle::setSize(x,y) to throw an exception, call abort(), choose the average of
the two parameters, or to be a no-op). Unfortunately all these hacks will
surprise users, since users are expecting width() == x and height() == y. The
one thing you must not do is surprise your users.
If it is important to you to retain the "Circle is a kind-of Ellipse"
inheritance relationship, you can weaken the promise made by Ellipse's
setSize(x,y). E.g., you could change the promise to, "This member function
might set width() to x and/or it might set height() to y, or it might do
nothing". Unfortunately this dilutes the contract into dribble, since the user
can't rely on any meaningful behavior. The whole hierarchy therefore begins to
be worthless (it's hard to convince someone to use an object if you have to
shrug your shoulders when asked what the object does for them).
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
(Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to
prevent users from changing the dimensions of an Ellipse, in which case it
would be a valid design choice to not have a setSize(x,y) method in Ellipse.
However this series of FAQs discusses what to do when you want to create a
derived class of a pre-existing base class that has an "unacceptable" method in
it. Of course the ideal situation is to discover this problem when the base
class doesn't yet exist. But life isn't always ideal...)
==============================================================================
[21.8] But I have a Ph.D. in Mathematics, and I'm sure a Circle is a kind of an
Ellipse! Does this mean Marshall Cline is stupid? Or that C++ is stupid?
Or that OO is stupid?
Actually, it doesn't mean any of these things. The sad reality is that it
means your intuition is wrong.
Look, I have received and answered dozens of passionate e-mail messages about
this subject. I have taught it hundreds of times to thousands of software
professionals all over the place. I know it goes against your intuition. But
trust me; your intuition is wrong.
The real problem is your intuitive notion of "kind of" doesn't match the OO
notion of proper inheritance (technically called "subtyping"). The bottom line
is that the derived class objects must be substitutable for the base class
objects. In the case of Circle/Ellipse, the setSize(x,y) member function
violates this substitutability.
You have three choices: [1] remove the setSize(x,y) member function from
Ellipse (thus breaking existing code that calls the setSize(x,y) member
function), [2] allow a Circle to have a different height than width (an
asymmetrical circle; hmmm), or [3] drop the inheritance relationship. Sorry,
but there simply are no other choices. Note that some people mention the
option of deriving both Circle and Ellipse from a third common base class, but
that's just a variant of option [3] above.
Another way to say this is that you have to either make the base class weaker
(in this case braindamage Ellipse to the point that you can't set its width and
height to different values), or make the derived class stronger (in this case
empower a Circle with the ability to be both symmetric and, ahem, asymmetric).
When neither of these is very satisfying (such as in the Circle/Ellipse case),
one normally simply removes the inheritance relationship. If the inheritance
relationship simply has to exist, you may need to remove the mutator member
functions (setHeight(y), setWidth(x), and setSize(x,y)) from the base class.
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
(Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to
prevent users from changing the dimensions of an Ellipse, in which case it
would be a valid design choice to not have a setSize(x,y) method in Ellipse.
However this series of FAQs discusses what to do when you want to create a
derived class of a pre-existing base class that has an "unacceptable" method in
it. Of course the ideal situation is to discover this problem when the base
class doesn't yet exist. But life isn't always ideal...)
==============================================================================
[21.9] But my problem doesn't have anything to do with circles and ellipses, so
what good is that silly example to me?
Ahhh, there's the rub. You think the Circle/Ellipse example is just a silly
example. But in reality, your problem is an isomorphism to that example.
I don't care what your inheritance problem is, but all (yes all) bad
inheritances boil down to the Circle-is-not-a-kind-of-Ellipse example.
Here's why: Bad inheritances always have a base class with an extra capability
(often an extra member function or two; sometimes an extra promise made by one
or a combination of member functions) that a derived class can't satisfy.
You've either got to make the base class weaker, make the derived class
stronger, or eliminate the proposed inheritance relationship. I've seen lots
and lots and lots of these bad inheritance proposals, and believe me, they all
boil down to the Circle/Ellipse example.
Therefore, if you truly understand the Circle/Ellipse example, you'll be able
to recognize bad inheritance everywhere. If you don't understand what's going
on with the Circle/Ellipse problem, the chances are high that you'll make some
very serious and very expensive inheritance mistakes.
Sad but true.
(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)
==============================================================================
SECTION [22]: Inheritance -- abstract base classes (ABCs)
[22.1] What's the big deal of separating interface from implementation?
Interfaces are a company's most valuable resources. Designing an interface
takes longer than whipping together a concrete class which fulfills that
interface. Furthermore interfaces require the time of more expensive people.
Since interfaces are so valuable, they should be protected from being tarnished
by data structures and other implementation artifacts. Thus you should
separate interface from implementation.
==============================================================================
[22.2] How do I separate interface from implementation in C++ (like Modula-2)?
Use an ABC[22.3].
==============================================================================
[22.3] What is an ABC?
An abstract base class.
At the design level, an abstract base class (ABC) corresponds to an abstract
concept. If you asked a mechanic if he repaired vehicles, he'd probably wonder
what kind-of vehicle you had in mind. Chances are he doesn't repair space
shuttles, ocean liners, bicycles, or nuclear submarines. The problem is that
the term "vehicle" is an abstract concept (e.g., you can't build a "vehicle"
unless you know what kind of vehicle to build). In C++, class Vehicle would be
an ABC, with Bicycle, SpaceShuttle, etc, being subclasses (an OceanLiner
is-a-kind-of-a Vehicle). In real-world OO, ABCs show up all over the place.
At the programming language level, an ABC is a class that has one or more pure
virtual[22.4] member functions. You cannot make an object (instance) of an
ABC.
==============================================================================
[22.4] What is a "pure virtual" member function?
A member function declaration that turns a normal class into an abstract class
(i.e., an ABC). You normally only implement it in a derived class.
Some member functions exist in concept; they don't have any reasonable
definition. E.g., suppose I asked you to draw a Shape at location (x,y) that
has size 7. You'd ask me "what kind of shape should I draw?" (circles,
squares, hexagons, etc, are drawn differently). In C++, we must indicate the
existence of the draw() member function (so users can call it when they have a
Shape* or a Shape&), but we recognize it can (logically) be defined only in
subclasses:
class Shape {
public:
virtual void draw() const = 0; // = 0 means it is "pure virtual"
// ...
};
This pure virtual function makes Shape an ABC. If you want, you can think of
the "= 0;" syntax as if the code were at the NULL pointer. Thus Shape promises
a service to its users, yet Shape isn't able to provide any code to fulfill
that promise. This forces any actual object created from a [concrete] class
derived from Shape to have the indicated member function, even though the base
class doesn't have enough information to actually define it yet.
Note that it is possible to provide a definition for a pure virtual function,
but this usually confuses novices and is best avoided until later.
==============================================================================
[22.5] How do you define a copy constructor or assignment operator for a class
that contains a pointer to a (abstract) base class? [UPDATED!]
[Recently fixed Circle and Square so they inherit from Shape thanks to Paul
Campbell (on 1/00).]
If the class "owns" the object pointed to by the (abstract) base class pointer,
use the Virtual Constructor Idiom[20.5] in the (abstract) base class. As usual
with this idiom, we declare a pure virtual[22.4] clone() method in the base
class:
class Shape {
public:
// ...
virtual Shape* clone() const = 0; // The Virtual (Copy) Constructor[20.5]
// ...
};
Then we implement this clone() method in each derived class:
class Circle : public Shape {
public:
// ...
virtual Shape* clone() const { return new Circle(*this); }
// ...
};
class Square : public Shape {
public:
// ...
virtual Shape* clone() const { return new Square(*this); }
// ...
};
Now suppose that each Fred object "has-a" Shape object. Naturally the Fred
object doesn't know whether the Shape is Circle or a Square or ... Fred's copy
constructor and assignment operator will invoke Shape's clone() method to copy
the object:
class Fred {
public:
Fred(Shape* p) : p_(p) { assert(p != NULL); } // p must not be NULL
~Fred() { delete p_; }
Fred(const Fred& f) : p_(f.p_->clone()) { }
Fred& operator= (const Fred& f)
{
if (this != &f) { // Check for self-assignment
Shape* p2 = f.p_->clone(); // Create the new one FIRST...
delete p_; // ...THEN delete the old one
p_ = p2;
}
return *this;
}
// ...
private:
Shape* p_;
};
==============================================================================
SECTION [23]: Inheritance -- what your mother never told you
[23.1] When my base class's constructor calls a virtual function, why doesn't
my derived class's override of that virtual function get invoked?
During the class Base's constructor, the object isn't yet a Derived, so if
Base::Base() calls a virtual[20] function virt(), the Base::virt() will be
invoked, even if Derived::virt() exists.
Similarly, during Base's destructor, the object is no longer a Derived, so when
Base::~Base() calls virt(), Base::virt() gets control, not the Derived::virt()
override.
You'll quickly see the wisdom of this approach when you imagine the disaster if
Derived::virt() touched a member object from class Derived. In particular, if
Base::Base() called the virtual function virt(), this rule causes Base::virt()
to be invoked. If it weren't for this rule, Derived::virt() would get called
before the Derived part of a Derived object is constructed, and Derived::virt()
could touch unconstructed member objects from the Derived part of a Derived
object. That would be a disaster.
==============================================================================
[23.2] Should a derived class replace ("override") a non-virtual function from
a base class?
It's legal, but it ain't moral.
Experienced C++ programmers will sometimes redefine a non-virtual function for
efficiency (e.g., if the derived class implementation can make better use of
the derived class's resources) or to get around the hiding rule[23.3]. However
the client-visible effects must be identical, since non-virtual functions are
dispatched based on the static type of the pointer/reference rather than the
dynamic type of the pointed-to/referenced object.
==============================================================================
[23.3] What's the meaning of, Warning: Derived::f(float) hides Base::f(int)?
It means you're going to die.
Here's the mess you're in: if Base declares a member function f(int), and
Derived declares a member function f(float) (same name but different parameter
types and/or constness), then the Base f(int) is "hidden" rather than
"overloaded" or "overridden" (even if the Base f(int) is virtual[20]).
Here's how you get out of the mess: Derived must have a using declaration of
the hidden member function. For example,
class Base {
public:
void f(int);
};
class Derived : public Base {
public:
using Base::f; // This un-hides Base::f(int)
void f(double);
};
If the using syntax isn't supported by your compiler, redefine the hidden Base
member function(s), even if they are non-virtual[23.2]. Normally this
re-definition merely calls the hidden Base member function using the :: syntax.
E.g.,
class Derived : public Base {
public:
void f(double);
void f(int i) { Base::f(i); } // The redefinition merely calls Base::f(int)
};
==============================================================================
[23.4] What does it mean that the "virtual table" is an unresolved external?
If you get a link error of the form
"Error: Unresolved or undefined symbols detected: virtual table for class Fred,"
you probably have an undefined virtual[20] member function in class Fred.
The compiler typically creates a magical data structure called the "virtual
table" for classes that have virtual functions (this is how it handles dynamic
binding[20.2]). Normally you don't have to know about it at all. But if you
forget to define a virtual function for class Fred, you will sometimes get this
linker error.
Here's the nitty gritty: Many compilers put this magical "virtual table" in the
compilation unit that defines the first non-inline virtual function in the
class. Thus if the first non-inline virtual function in Fred is wilma(), the
compiler will put Fred's virtual table in the same compilation unit where it
sees Fred::wilma(). Unfortunately if you accidentally forget to define
Fred::wilma(), rather than getting a Fred::wilma() is undefined, you may get a
"Fred's virtual table is undefined". Sad but true.
==============================================================================
--
Marshall Cline / 972-931-9470 / mailto:[email protected]
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |