If you mean to fix this
// an example base class, forcing some invariant on it's state
class Base {
int i; // guaranteed to be zero or MAXINT
public:
Base( bool isClear ): i( isClear ? 0 : MAXINT ) {}
void increment() { i = MAXINT; }
void decrement() { i = 0; }
bool isMax() const { return i == MAXINT; }
bool isZero() const { return i == 0; }
void checkInvariants() const { assert( isMax() || isZero() ); }
};
class Sub : public Base {
public:
Sub( int intended_i ) : i(intended_i) {} // error: i not accessible!
};
By making the Base::i
member protected, you're in for a piece of fragile design. It's the Base
class' responsibility to initialize it's members. When opening up the members to subclasses or to the public, Base
can no longer guarantee that it's members are subject to it's invariant.
In the above code, this would mean that certain instances of Base
can fail to 'act' like Base
instances:
Sub s( 10 );
s.checkInvariants(); // will trigger the assert!
On top of that, the subclass will have to be changed when deciding to change members in the superclass.
Subclasses are really also clients of the base class. They should access the same interface as the client code. This keeps the design flexible (loose coupling).
A way to get around copying constructor arguments over and over again is wrapping it in a structure. Especially when you suspect the Base class to be subject to change frequently, this can separate the concerns a lot:
class Base {
int i;
public:
struct Args { bool isClear; };
Base( const Args & args ): i( args.isClear ? 0 : MAXINT ) {}
};
class Sub {
bool b;
public:
struct Args { Base::Args base; bool b; };
Sub( const Args& args ): Base( args.base ), b( args.b ) {}
};
Now, adding a member to the base class becomes possible without changing the subclass: simply extend the Base::Args
structure, and you're done. (Note: the client code will still need to fill in the extra arguments for the base class).