Note: the following is C++03 code, but we expect a move to C++11 in the next two years, so we must keep that in mind.
I'm writing a guideline (for newbies, among others) about how to write an abstract interface in C++. I've read both articles of Sutter on the subject, searched the internet for examples and answers, and made some tests.
This code must NOT compile!
void foo(SomeInterface & a, SomeInterface & b)
{
SomeInterface c ; // must not be default-constructible
SomeInterface d(a); // must not be copy-constructible
a = b ; // must not be assignable
}
All the behaviors above find the source of their problem in slicing: The abstract interface (or non-leaf class in the hierarchy) should not be constructible nor copiable/assignable, EVEN if the derived class can be.
0th Solution: the basic interface
class VirtuallyDestructible
{
public :
virtual ~VirtuallyDestructible() {}
} ;
This solution is plain, and somewhat naive: It fails all our constraints: It can be default-constructed, copy-constructed and copy-assigned (I'm not even sure about move constructors and assignment, but I have still 2 years to figure it out).
- We can't declare the destructor pure virtual because we need to keep it inline, and some of our compilers won't digest pure virtual methods with inline empty body.
- Yes, the only point of this class is to make the implementers virtually destructible, which is a rare case.
- Even if we had an additional virtual pure method (which is the majority of the cases), this class would be still copy-assignable.
So, no...
1st Solution: boost::noncopyable
class VirtuallyDestructible : boost::noncopyable
{
public :
virtual ~VirtuallyDestructible() {}
} ;
This solution is the best, because it is plain, clear, and C++ (no macros)
The problem is that it still doesn't work for that specific interface because VirtuallyConstructible can still be default-constructed.
- We can't declare the destructor pure virtual because we need to keep it inline, and some of our compilers won't digest it.
- Yes, the only point of this class is to make the implementers virtually destructible, which is a rare case.
Another problem is that classes implementing the non-copyable interface must then explicitly declare/define the copy constructor and the assignment operator if they need to have those methods (and in our code, we have value classes that can still be accessed by our client through interfaces).
This goes against the Rule of Zero, which is where we want to go: If the default implementation is ok, then we should be able to use it.
2nd Solution: make them protected!
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
// With C++11, these methods would be "= default"
MyInterface() {}
MyInterface(const MyInterface & ) {}
MyInterface & operator = (const MyInterface & ) { return *this ; }
MyInterface(MyInterface && ) noexcept {}
MyInterface & operator = (MyInterface && ) noexcept { return *this ; }
} ;
This pattern follows the technical constraints we had (at least in user code): MyInterface cannot be default-constructed, cannot be copy-constructed and cannot be copy-assigned.
Also, it imposes no artificial constraints to implementing classes, which are then free to follow the Rule of Zero, or even declare a few constructors/operators as "= default" in C++11/14 without problem.
Now, this is quite verbose, and an alternative would be using a macro, something like:
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;
The protected must remain outside the macro (because it has no scope).
Correctly "namespaced" (that is, prefixed with the name of your company or product), the macro should be harmless.
And the advantage is that the code is factored in one source, instead of being copy pasted in all interfaces. Should the move-constructor and move-assignment be explicitly disabled the same way in the future, this would be a very light change in the code.
Conclusion
- Am I paranoid to want the code to be protected against slicing in interfaces? (I believe I'm not, but one never knows...)
- What is the best solution among the ones above?
- Is there another, better solution?
Please remember that this is a pattern that will serve as a guideline for newbies (among others), so a solution like: "Each case should have its implementation" is not a viable solution.
Bounty and results
I awarded the bounty to coredump because of the time spent to answer the questions, and the relevance of the answers.
My solution to the problem will probably go to something like that:
class MyInterface
{
DECLARE_CLASS_AS_INTERFACE(MyInterface) ;
public :
// the virtual methods
} ;
... with the following macro:
#define DECLARE_CLASS_AS_INTERFACE(ClassName) \
public : \
virtual ~ClassName() {} \
protected : \
ClassName() {} \
ClassName(const ClassName & ) {} \
ClassName & operator = (const ClassName & ) { return *this ; } \
ClassName(ClassName && ) noexcept {} \
ClassName & operator = (ClassName && ) noexcept { return *this ; } \
private :
This is a viable solution for my problem for the following reasons:
- This class cannot be instantiated (the constructors are protected)
- This class can be virtually destroyed
- This class can be inherited without imposing undue constraints on inheriting classes (e.g. the inheriting class could be by default copiable/moveable)
- The use of the macro means the interface "declaration" is easily recognizable (and searchable), and its code is factored in one place making it easier to modify (a suitably prefixed name will remove undesirable name clashes)
Note that the other answers gave valuable insight. Thank you to all you who gave it a shot.
Note that I guess I can still put another bounty on this question, and I value enlightning answers enough that should I see one, I would open a bounty just to assign it to that answer.
virtual void bar() = 0;
for example? That would prevent your interface from being instanciated.virtual ~VirtuallyDestructible() = 0
and virtual inheritance of interface classes (with abstract members, only). You might omit that VirtuallyDestructible, likely.