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).
|