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:
- 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. - The base class can't make guarantees about what
speak()
does. A derived class may forget the new line, or write it tocerr
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
Add the Where Clause Dynamically in Entity Framework
Deploying ASP.NET Website on Linux Server
Dotnet.Highcharts: Cost Not Plotted Against the Correct Date
Signalr Owin Self-Host on Linux/Mono Socketexception When Clients Lose Connection
How to Target Mono Framework from Vs2015
How to Display the Output of SQL "Print" Command in C#
Escaping the Escape Character Does Not Work - SQL Like Operator
How to Set PDF Paragraph or Font Line-Height with Itextsharp
Regex to Get Src Value from an Img Tag
Entity Framework Specification Pattern Implementation
Getting @@Identity from Tableadapter
How to Call a Function of a C++ Dll That Accepts a Parameter of Type Stringstream from C#
ASP.NET MVC 4 + Ninject MVC 3 = No Parameterless Constructor Defined for This Object
How to Ensure Entries with Non-Overlapping Time Ranges