The first time most programmers meet virtual, they reach for it the obvious way: any function a derived class might want to override gets marked virtual in the public interface. It works, the hierarchy compiles, and polymorphism does what it advertises. The trouble is that this design hands every subclass a blank cheque — they can replace not just the behaviour of an operation but the entire contract around it. The Non-Virtual Interface idiom (NVI), popularised by Herb Sutter, fixes that by inverting the usual defaults: the public face of the class is non-virtual, and the customisation points are private virtuals.
Three things to take away:
- Public functions should be non-virtual; virtual functions should be private (or protected when derived classes need to call up).
- The base class owns the contract — pre-checks, post-checks, invariants — and calls into the virtual step internally.
- A private virtual can still be overridden, because access control and virtual dispatch are independent in C++.
The naive shape: public virtuals everywhere
A renderer hierarchy where every subclass draws differently is the textbook polymorphism example:
class Renderer {
public:
virtual ~Renderer() = default;
virtual void render(const Scene& scene)= 0;
};
class OpenGLRenderer : public Renderer {
public:
void render(const Scene& scene) override{
// bind framebuffer, draw, swap buffers...
}
};
This compiles and works. But the base class has no say in what happens around render. If every renderer needs to validate the scene first, log timing, or update a frame counter afterwards, there is nowhere to put that code except inside each override — and there is no way to enforce that subclass authors remember to do it. The contract lives in documentation, not in the type system.
The NVI shape: non-virtual public, private virtual
NVI splits the operation into two functions: a non-virtual public one that the world calls, and a private virtual one that subclasses override:
class Renderer {
public:
virtual ~Renderer() = default;
void render(const Scene& scene){ // non-virtual: the contract
assert(scene.isValid());
++m_frameCount;
const auto start = Clock::now();
renderImpl(scene); // the customisable step
m_lastFrameTime = Clock::now() - start;
}
private:
virtual void renderImpl(const Scene& scene)= 0;
int m_frameCount { 0 };
Duration m_lastFrameTime { };
};
class OpenGLRenderer : public Renderer {
private:
void renderImpl(const Scene& scene) override{
// bind framebuffer, draw, swap buffers...
}
};
The public render is now a template method in the design-pattern sense. It owns the surrounding logic — the assertion, the frame counter, the timing — and delegates the variable part to renderImpl. Every subclass automatically participates in the contract because the base class is the only thing that calls the virtual function. There is no way for a subclass to forget the pre-check or the post-check, because subclasses don’t write the calling code at all.
“Wait — can a private virtual even be overridden?”
This is the part that surprises people. In C++, access control and virtual dispatch are independent mechanisms. Access control governs whether a name can be named at a call site; virtual dispatch governs which function a virtual call resolves to at run time. They don’t talk to each other.
class Base {
private:
virtual void step(){ /* ... */ }
public:
void run(){ step(); } // OK — Base may name its own private member
};
class Derived : public Base {
private:
void step() override{ /* ... */ } // OK — overriding is not "naming"
};
The override is legal because overriding doesn’t require the derived class to call the base function or even refer to it by name — it just declares a function with the matching signature. Access control never enters the picture. When run calls step, virtual dispatch picks Derived::step even though it is private, because the call is being made from inside Base, where the name is accessible.
The practical consequence: NVI gets to enforce “only the base class calls this” through access control, and still gets full virtual dispatch. Subclasses can override, but no one — not even the subclasses themselves — can call the virtual function directly.
When derived classes need to call up: protected virtual
Sometimes a derived class needs to invoke the base implementation as part of its override (think chained initialisation, or extending behaviour rather than replacing it). For those cases, make the virtual protected rather than private:
class Widget {
public:
void paint(Canvas& c){
prepare(c);
paintImpl(c);
finalise(c);
}
protected:
virtual void paintImpl(Canvas& c)= 0;
};
class FramedWidget : public Widget {
protected:
void paintImpl(Canvas& c) override{
drawFrame(c);
// No call to Widget::paintImpl — it's pure virtual here.
}
};
protected keeps the virtual out of the public API while still allowing derived classes to call it on themselves or their base. The default should be private; relax to protected only when chaining is genuinely part of the design.
When NVI isn’t worth it
NVI adds a layer of indirection and a second function per customisation point. For interfaces where the base class genuinely has nothing to add — pure abstract interfaces with no shared state, no invariants, no pre- or post-conditions — that overhead buys nothing. A pure interface like class Drawable { public: virtual void draw() = 0; }; is fine as written.
The idiom earns its keep when the base class has something to say around the call: an invariant to check, state to update, behaviour to log, or a sequence of steps where only some are customisable. Whenever you find yourself writing the same boilerplate at the top or bottom of every override, NVI is the right refactor.