Flylib.com

Books Software

 
 
 

FAQ 8.15 Is parking-lot-for-cars a kind-of parking-lot-for-arbitrary-vehicles (assuming parking-lot-for-vehicles allows parking any kind-of vehicle)?

FAQ 8.15 Is parking-lot-for- cars a kind-of parking-lot-for-arbitrary-vehicles ( assuming parking-lot-for-vehicles allows parking any kind-of vehicle)?

graphics/new_icon.gif

NO!

This is another specific example of the general guideline presented earlier.

In the following, Vehicle is an ABC and Car and NuclearSubmarine are concrete kinds-of Vehicle .

graphics/08fig03.gif

#include <iostream>
using namespace std;

class Vehicle {
public:
  virtual ~Vehicle();
};

Vehicle::~Vehicle()
{ }

class Car : public Vehicle {
public:
  virtual void startEngine() throw();
};
void Car::startEngine() throw()
{ cout << "starting a Car...\n"; }

class NuclearSubmarine : public Vehicle {
public:
  virtual void launchMissile();
};

void NuclearSubmarine::launchMissile() throw()
{ cout << "starting a War...\n"; }

If a container of Car was a kind-of container of Vehicle , someone might put a NuclearSubmarine inside the container of Car , then remove the NuclearSubmarine thinking it was a Car .

This is an egregious error. When the startEngine() member function is called, the launchMissile() may actually be executed (depending on the compiler and implementation). Thus, starting the car's engine might inadvertently start World War III! See the previous FAQ to see the user code that might cause this to happen.

The root problem is bad inheritance. A bad design can't be patched with a little extra inheritance. Throwing more inheritance at an already defective design will usually make it worse . If the design is broken, it needs to be fixed, not patched with clever coding tricks.

FAQ 8.16 Is array-of Derived a kind-of array-of Base ?

No! And the compiler usually doesn't detect this error.

This is another specific example of the general guideline presented earlier. For example, suppose class Base has some virtual functions:

#include <iostream>
using namespace std;

class Base {
public:
  Base() throw();
  virtual ~Base() throw();
  virtual void f() throw();
};

Base::Base()
{ }
Base::~Base()
{ }

void Base::f() throw()
{ cout << "Base::f()\n" << flush; }

Suppose class Derived has some data, such as integer i_ :

class Derived : public Base {
public:
  Derived() throw();
  virtual void f() throw();
protected:
  int i_;
};

Derived::Derived() throw()
: Base()
, i_(42)
{ }

void Derived::f() throw()
{ cout << "Derived::f()\n" << flush; }

Making a Base pointer refer to the first element in an array-of Derived objects is okay, provided users never perform pointer arithmetic (such as applying the subscript operator) using the Base pointer. Subscripting into the array using the Base pointer is wrong since it will use sizeof(Base) for the pointer arithmetic rather than sizeof(Derived) . Thus, subscripting into the array using the Base pointer may not refer to any Derived object:

void sample(Base* b) throw()
{
  cout << "b[0].f(): " << flush;
  b[0].f();
  cout << "b[1].f(): " << flush;
  b[1].f();

<-- 1

}

int main()
{
  Derived d[10];
  sample(d);
}

(1) Bang!

To understand what happened in function sample() , imagine a typical 32-bit computer that implements virtual functions in the usual way: a single virtual pointer in the object. In this case sizeof(Base) might be 4 (that is, one machine word for the virtual pointer), and sizeof(Derived) might be 8 (the Derived class objects have whatever size was in the Base plus the data from Derived ). But since the compiler only knows that b points to Base in function sample() , it will use sizeof(Base) when computing subscripts. So b[1] will be 4 bytes after the beginning of the array, which is somewhere in the middle of object d[0] !

Regardless of the specifics of the machine used, it is very unlikely that b[1] will refer to the same address as d[1] since sizeof(Derived) is different than sizeof(Base) . So the best possible outcome would be an immediate system crash (which is extremely likely; try it!).

This underscores the advantage of using an array-like class instead of using a C++ array. The compiler would have detected the error in the preceding problem if a vector<Derived> had been used rather than a Derived[] ( vector<T> is the standard array-like template; see FAQ 28.13). For example, attempting to pass a vector<Derived> to sample(vector<Base>& a ) would have caused a compile-time error message; see FAQ 8.17. (Remember: don't use pointer casts to "cover up" error messages. When you get an error message, fix the underlying problem rather than simply trying to get the compiler to stop generating error messages).