CALL-BY-VALUE ISSUES

  1. Only the class-specific portion of the object is copied. So if your function is prototyped as foo(A a), but foo is actually passed a subclass of A, the member variables specific to that subclass are not copied. (Winston Section 37.)

Solution: pointer to a.

  1. When a function return, all local variables are destroyed. If they are objects (and this includes objects passed as parameters), then they are also destroyed. This can be a problem because by default, when the compiler is creating a copy of an object to pass to a function, it only copies member variables. If a member variable happens to be a pointer, then only the pointer (not what the pointer points to) is copied. Hence, if your function has the prototype foo(A a), then if a has some member variable that is a pointer, then the memory allocated for it will be destroyed--the callee will destroy memory required by the caller.

This problem can be fixed by converting the function from call-by-value to call-by-reference, or defining your own copy constructor. (You can also define a "private" copy constructor, in which case the compiler will emit an error.)

(Winston Section 44)

WEIRDNESS

1.

Can only overload operators when they act on objects, not primitive types.

2.

All the numerous ways to construct and initialise variables are confusing and inconsistent.

complex b = 3;

is the same as

complex b(3);

is the same as

complex b = complex(3);

might be the same as

complex b = { 3 }

constructors:

Default Arguments

complex() : re(0), im(0) {} complex(double r) : re(r), im(0) {} complex(double r, double i) : re(r), im(i) {}

complex(double r=0, double i=0) : re(r), im(i) { }

Calling base class's constructor

derived(int foo, int bar) : base(foo), _bar(bar) { }

QUESTIONS

1.

How, from one constructor, can you call another constructor (e.g. no-argument constructor)?

struct foo { foo(int i) { // how to call foo() } };

(You might want to do this if you have debugging or instance-counting code in the no-argument constructor.)

ANNOYING THINGS

1.

If, when writing a class, you think that a derived class will ever require a virtual destructor, then you have to define a virtual destructor, even if the base class does not require it. e.g.

struct foo { };

struct bar : public foo { ~bar() { // whatever } };

int main(void) { foo* f = new bar;

delete f; // bar's destructor will not be called!

}

The solution is to define foo as follows

struct foo { virtual ~foo() { } };

(bar's destructor should probably also be marked as virtual as well, for clarity.)

This, of course, introduces the overhead of virtual functions, even if foo does not need it.

Another related problem is that if foo does have a destructor, but it is not virtual, then it will be foo's destructor, not bar's that is called.

struct foo { ~foo() { // whatever } };

struct bar : public foo { ~bar() { // whatever } };

int main(void) { foo* f = new bar;

delete f; // bar's destructor will not be called!

}

In order to fix these mistakes, it is necessary that you refer back to the base class when writing the derived class. This is not only annoying, but easy to forget.