Non-Virtual Interface Design Pattern in C#/C++

Non-virtual interface design pattern in C#/C++

The essence of the non-virtual interface pattern is that you have private virtual functions, which are called by public non-virtual functions (the non-virtual interface).

The advantage of this is that the base class has more control over its behaviour than it would if derived classes were able to override any part of its interface. In other words, the base class (the interface) can provide more guarantees about the functionality it provides.

As a simple example, consider the good old animal class with a couple of typical derived classes:

class Animal
{
public:
virtual void speak() const = 0;
};

class Dog : public Animal
{
public:
void speak() const { std::cout << "Woof!" << std::endl; }
};

class Cat : public Animal
{
public:
void speak() const { std::cout << "Meow!" << std::endl; }
};

This uses the usual public virtual interface that we're used to, but it has a couple of problems:

  1. Each derived animal is repeating code -- the only part that changes is the string, yet each derived class needs the whole std::cout << ... << std::endl; boilerplate code.
  2. The base class can't make guarantees about what speak() does. A derived class may forget the new line, or write it to cerr or anything for that matter.

To fix this, you can use a non-virtual interface that is supplemented by a private virtual function that allows polymorphic behaviour:

class Animal
{
public:
void speak() const { std::cout << getSound() << std::endl; }
private:
virtual std::string getSound() const = 0;
};

class Dog : public Animal
{
private:
std::string getSound() const { return "Woof!"; }
};

class Cat : public Animal
{
private:
std::string getSound() const { return "Meow!"; }
};

Now the base class can guarantee that it will write out to std::cout and end with a new line. It also makes maintenance easier as derived classes don't need to repeat that code.

Herb Sutter wrote a good article on non-virtual interfaces that I would recommend checking out.

Is the Non-Virtual Interface (NVI) idiom as useful in C# as in C++?

I think the explanation is simply that in C#, "traditional" Java-style OOP is much more ingrained, and NVI runs counter to that. C# has a real interface type, whereas NVI relies on the "interface" actually being a base class. That's how it's done in C++ anyway, so it fits naturally there.

In C#, it can still be done, and it is still a very useful idiom (far more so, I'd say, than "normal" interfaces), but it requires you to ignore a built-in language feature.

Many C# programmers just wouldn't think of a NVI class as being "a proper interface". I think this mental resistance is the only reason why it's less common in C#.

Why are C# interface methods not declared abstract or virtual?

For the interface, the addition of the abstract, or even the public keywords would be redundant, so you omit them:

interface MyInterface {
void Method();
}

In the CIL, the method is marked virtual and abstract.

(Note that Java allows interface members to be declared public abstract).

For the implementing class, there are some options:

Non-overridable: In C# the class doesn't declare the method as virtual. That means that it cannot be overridden in a derived class (only hidden). In the CIL the method is still virtual (but sealed) because it must support polymorphism regarding the interface type.

class MyClass : MyInterface {
public void Method() {}
}

Overridable: Both in C# and in the CIL the method is virtual. It participates in polymorphic dispatch and it can be overridden.

class MyClass : MyInterface {
public virtual void Method() {}
}

Explicit: This is a way for a class to implement an interface but not provide the interface methods in the public interface of the class itself. In the CIL the method will be private (!) but it will still be callable from outside the class from a reference to the corresponding interface type. Explicit implementations are also non-overridable. This is possible because there's a CIL directive (.override) that will link the private method to the corresponding interface method that it's implementing.

[C#]

class MyClass : MyInterface {
void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
.override MyInterface::Method
}

In VB.NET, you can even alias the interface method name in the implementing class.

[VB.NET]

Public Class MyClass
Implements MyInterface
Public Sub AliasedMethod() Implements MyInterface.Method
End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
.override MyInterface::Method
}

Now, consider this weird case:

interface MyInterface {
void Method();
}
class Base {
public void Method();
}
class Derived : Base, MyInterface { }

If Base and Derived are declared in the same assembly, the compiler will make Base::Method virtual and sealed (in the CIL), even though Base doesn't implement the interface.

If Base and Derived are in different assemblies, when compiling the Derived assembly, the compiler won't change the other assembly, so it will introduce a member in Derived that will be an explicit implementation for MyInterface::Method that will just delegate the call to Base::Method.

So you see, every interface method implementation must support polymorphic behavior, and thus must be marked virtual on the CIL, even if the compiler must go through hoops to do it.

Compile-Time Interfaces (non-virtual)

Just make an adapter:

#include <string>
#include <iostream>

// the original data class. Does not depend on adapters,
// thus has no reasons to be changed when a new adapter is added,
// completely SRP compliant
struct data
{
std::string str{"data"};
};

// this may be added in a completely separate header without the need
// to ever modify the data class
class view
{
public:
constexpr view(const data& ref)
: ref_(ref)
{}

const std::string& str() const
{
return ref_.str;
}

private:
const data& ref_;
};

// this function uses an interface, but doesn't own the resources
void print(view v)
{
std::cout << v.str();
}

int main()
{
// no heap allocation is needed for an adapter
print(data{"data"});
}

https://godbolt.org/z/hjEzMzYYs - see example with -O3

This assumes that you are using views as interfaces and the interface holders do not own the underlying data.

Adaptors are cleaner since they do not force view types as dependencies on data.

If you want to hide data from an adaptor's type signature, use type erasure.

Defining interfaces (abstract classes without members) in C++

You ask a lot of questions, but I'll give it a shot.

By an interface (C# terminology) I mean an abstract class with no data members.

Nothing specifically like a C# interface exists. A C++ abstract base class comes the closest, but there are differences (for example, you will need to define a body for the virtual destructor).

Thus, such a class only specifies a contract (a set of methods) that sub-classes must implement. My question is: How to implement such a class correctly in modern C++?

As a virtual base class.

Example:

class OutputSink
{
public:

~OutputSink() = 0;

// contract:
virtual void put(std::vector<std::byte> const& bytes) = 0;
};

OutputSink::~OutputSink() = default;

Hence I guess it should be declared with the struct keyword, since it only contains public members anyway.

There are multiple conventions for when to use a structure versus a class. The guideline I recommend (hey, you asked for opinions :D) is to use structures when you have no invariants on their data. For a base class, please use the class keyword.

"A polymorphic class should suppress copying"

Mostly true. I have written code where the client code didn't perform copies of the inherited classes, and the code worked just fine (without prohibiting them). The base classes didn't forbid it explicitly, but that was code I was writing in my own hobby project. When working in a team, it is good practice to specifically restrict copying.

As a rule, don't bother with cloning, until you find an actual use case for it in your code. Then, implement cloning with the following signature (example for my class above):

virtual std::unique_ptr<OutputSink> OutputSink::clone() = 0;

If this doesn't work for some reason, use another signature (return a shared_ptr for example). owner<T> is a useful abstraction, but that should be used only in corner cases (when you have a code base that imposes on you the use of raw pointers).

An interface should consist only of public methods. It should declare [...]. It should [...]. It should be derived using public virtual.

Don't try to represent the perfect C# interface in C++. C++ is more flexible than that, and rarely will you need to add a 1-to-1 implementation of a C# concept in C++.

For example, in base classes in C++ I sometimes add public non-virtual function implementations, with virtual implementations:

class OutputSink
{
public:
void put(const ObjWithHeaderAndData& o) // non-virtual
{
put(o.header());
put(o.data());
}

protected:
virtual void put(ObjectHeader const& h) = 0; // specialize in implementations
virtual void put(ObjectData const& d) = 0; // specialize in implementations
};

thus the abstract base-class always needs to declare a default constructor?! Am I misunderstanding something?

Define the rule of 5 as needed. If code doesn't compile because you are missing a default constructor, then add a default constructor (use the guidelines only when they make sense).

Edit: (addressing comment)

as soon as you declare a virtual destructor, you have to declare some constructor for the class to be usable in any way

Not necessarily. It is better (but actually "better" depends on what you agree with your team) to understand the defaults the compiler adds for you and only add construction code when it differs from that. For example, in modern C++ you can initialize members inline, often removing the need for a default constructor completely.



Related Topics



Leave a reply



Submit