Reputation: 14474
The Non-virtual Interface idiome (NVI) is pretty self explanatory: You don't write public virtual
functions, but public
functions that call a private virtual
implementation function, like so:
class Object{
virtual void v_load();
public:
void load(){ v_load(); }
}
This enables you, the base class author, to check and enforce pre- and post-conditions or apply other functions so the author of deriving classes can't forget about them.
Now when you are the deriving author, you may want to write a base class yourself - let's call it Pawn
- that extends on the functionality of load()
and therefore has to override v_load()
. But now you are facing a problem:
When you override v_load()
, other clients that want to derive from your class, will always overwrite that behaviour, and they can not call Pawn::v_load()
because it is a private
function, neither can they call Pawn::load()
because it is defined as { v_load; }
in Object
which will of course lead to an infinite loop. Additionally, requiring them to do so could lead to mistakes when they forget that call. If I would want them to enable that, I would have to specify the acces to v_load()
as protected
in Object
, which seems like an ugly solution as it would weaken the encapsulation of Object
greatly.
You could of course still override v_load()
to call a new function v_pawnLoad()
, which is then overridden by clients, but that seems very error-prone as a lot of clients will probably overload the wrong function.
So, how can I design Pawn
in such a way that clients can still override v_load()
while keeping the ability to check pre-conditions or call other functions and (if possible) not enabling, let alone requiring clients of Object
or Pawn
to call the base v_load()
implementation?
Upvotes: 2
Views: 171
Reputation: 13278
load
's behaviour, then put the code you currently have in v_load
in load
then call an empty v_load
in the end.v_load
protected
if you want to let people choose between "replacing" or "extending".As a bonus, in all these 3 variants you can change "allow" with "force" by making your v_load
a pure virtual if you have no default behaviour.
If you wish to limit the override to your Pawn
child class, add the final
keyword to v_load
in Pawn
and use another virtual function to allow children of Pawn
to customise its behaviour.
Upvotes: 2
Reputation: 2277
How about mixin' in some CRTP?
#include <iostream>
class BaseObject
{
private:
virtual void v_load() = 0;
public:
void load() { v_load(); }
};
template<typename Derived>
class Object : public BaseObject
{
private:
virtual void v_load() { static_cast<Derived&>(*this).load(); }
};
class Pawn : public Object<Pawn>
{
public:
void load() { std::cout << "Pawn::load()" << std::endl; }
};
class BlackPawn : public Pawn
{
private:
virtual void v_load() {
std::cout << "BlackPawn::v_load()" << std::endl;
std::cout << "- "; Pawn::load();
}
public:
void load() {
std::cout << "BlackPawn::load()" << std::endl;
std::cout << "- "; Pawn::load();
}
};
class BigBlackPawn : public BlackPawn
{
private:
virtual void v_load() {
std::cout << "BigBlackPawn::v_load()" << std::endl;
std::cout << "- "; BlackPawn::load();
}
public:
void load() {
std::cout << "BigBlackPawn::load()" << std::endl;
std::cout << "- "; BlackPawn::load();
}
};
template<typename T>
void load(T& x)
{
x.load();
}
void vload(BaseObject& x)
{
x.load();
}
int main()
{
Pawn p;
BlackPawn bp;
BigBlackPawn bbp;
load(p);
load(bp);
load(bbp);
std::cout << std::endl;
vload(p);
vload(bp);
vload(bbp);
}
Output on ideone.
Upvotes: 0