Good C++ interface has default virtual destructor
It is easy to make things hard, but hard to make them easy
Good C++ interface has default virtual destructor
As simple as this:
class PaymentService {
public:
virtual ~PaymentService() = default;
virtual void Pay(int employee_id) = 0;
};
When interface has non-virtual destructor, derived (sub-) classes do not have clear way to release their resources and hence should be avoided. If someone defines an interface without virtual destructor, it is clearly a bug.
virtual tells the user: "The class is designed for inheritance".
default describes the intention better than default implementation, saying "We don't plant to do anything tricky here."
Also, The derived classes still keep have their move and copy operations despite the base class (interface) has a user-declared destructor and hence doesn't have implicitly declared/defined move operations (See Implicitly-declared move constructor). In order to test it, one can do the following:
class PaymentService {
public:
virtual ~PaymentService() = default;
};
class DerivedService: public PaymentService {
public:
std::string value = "value";
};
template <typename T>
void Consume(T&& value) {
T unusedTmp = std::forward<T>(value);
}
int main() {
auto derivedService = DerivedService();
std::cout << "Value before consume: \""
<< derivedService.value << '"' << '\n';
Consume(std::move(derivedService));
std::cout << "Value after consume: \""
<< derivedService.value << '"' << '\n';
return 0;
}
The output is
Value before consume: "value"
Value after consume: ""
As expected, the field of DerivedService was moved and has the string has empty (moved-out) state.
Alternative: Declare everything final.
Keyword final didn't exist prior C++11 (final specifier) and there was no simple way to define a class which is not designed to be inherited from. Many C++ developers adopted a rule which says "Do not inherit from a class which doesn't have virtual destructor". And it make sense, because sub-classes don't have a reasonable way to release their resources.
In some other placer, developers adopts even stricter rule "Do not inherit from a class which is not designed to be inherited from", which is, in most cases, stated by documentation. Such rule is easy to use because most of the classes are not designed to be inherited from.
But, what about final (post-C++11)? Unfortunately final has its own problems. As an example, final makes the code to rigid to refactoring. If someone decides temporarily inherit from our class in order to provide a stub for the new implementation, they wouldn't be able. And they would have to fix the class to be non-final first. Which might break some other assumptions about the code.