What is the pattern for a safe interface in C++

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).

  1. 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.
  2. Yes, the only point of this class is to make the implementers virtually destructible, which is a rare case.
  3. 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.

  1. We can’t declare the destructor pure virtual because we need to keep it inline, and some of our compilers won’t digest it.
  2. 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.

8

The canonical way to create an interface in C++ is to give it a pure virtual destructor. This ensures that

  • No instances of the interface class itself can be created, because C++ does not allow you to create an instance of an abstract class. This takes care of the not constructible requirements (both default and copy).
  • Calling delete on a pointer to the interface does the right thing: it calls the destructor of the most-derived class for that instance.

just having a pure virtual destructor doesn’t prevent assignment on a reference to the interface. If you need that to fail as well, then you must add a protected assignment operator to your interface.

Any C++ compiler should be able to handle a class/interface like this (all in one header file):

class MyInterface {
public:
  virtual ~MyInterface() = 0;
protected:
  MyInterface& operator=(const MyInterface&) { return *this; } // or = default for C++14
};

inline MyInterface::~MyInterface() {}

If you do have a compiler that chokes on this (which means it must be pre-C++98), then your option 2 (having protected constructors) is a good second-best.

Using boost::noncopyable is not advisable for this task, because it sends the message that all classes in the hierarchy should be non-copyable and it can thus create confusion for more experienced developers who wouldn’t be familiar with your intentions for using it like this.

2

Am I paranoid…

  • Am I paranoid to want the code to be protected against slicing in interfaces? (I believe I’m not, but one never knows…)

Isn’t this a risk management issue?

  • do you fear that a bug related to slicing is likely to be introduced?
  • do you think it can go unnoticed and provoke unrecoverable bugs?
  • to what extent are you willing to go to avoid slicing?

Best solution

  • What is the best solution among the ones above?

Your second solution (“make them protected”) looks good, but bear in mind that I am not a C++ expert.
At least, the invalid usages seem to be correctly reported as erroneous by my compiler (g++).

Now, do you need macros? I would say “yes”, because even though you don’t say what is the purpose of the guideline you are writing, I guess this is to enforce a particular set of best practices in your product’s code.

For that purpose, macros can help detect when people effectively apply the pattern: a basic filter of commits can tell you whether the macro was used:

  • if used, then the pattern is likely to be applied, and more importantly, correctly applied (just check that there is a protected keyword),
  • if not used, you can try to investigate why it wasn’t.

Without macros, you have to inspect whether the pattern is necessary and well-implemented in all cases.

Better solution

  • Is there another, better solution?

Slicing in C++ is nothing more than a peculiarity of the language.
Since your are writing a guideline (esp. for newbies), you should focus on teaching and not just enumerating “coding rules”.
You have to make sure that you really explain how and why slicing occurs, along with examples and exercices (don’t reinvent the wheel, get inspiration from books and tutorials).

For example, the title of an exercise could be “What is the pattern for a safe interface in C++?”

So, your best move would be to ensure that your C++ developpers understand what is going on when slicing occurs. I am convinced that if they do, they won’t make as many mistakes in the code as you’d fear, even without formally enforcing that particular pattern (but you can still enforce it, compiler warnings are good).

About the compiler

You say :

I have no power on the choice of compilers for this product,

Often people will say “I don’t have the right to do [X]”, “I’m not supposed to do [Y]…”, … because they think this is not possible, and not because they tried or asked.

It is probably part of your job description to give your opinion regarding technical issues; if you really think the compiler is the perfect (or unique) choice for your problem domain, then use it. But you also said “pure virtual destructors with inline implementation is not the worst choking point I’ve seen”; from my understanding, the compiler is so special that even knowledgeable C++ developpers have difficulties using it: your legacy/in-house compiler is now technical debt, and you have the right (the duty?) to discuss that issue with other developpers and managers.

Try to assess the cost of keeping the compiler vs. the cost of using another one:

  1. What does the current compiler brings you that no other one can?
  2. Is your product code easily compilable using another compiler? Why not?

I don’t know your situation, and in fact you probably have valid reasons to be tied to a specific compiler.
But in the case this is just plain inertia, the situation won’t ever evolve if you or your coworkers don’t report productivity or techical debt problems.

5

The problem of slicing is one, but certainly not the only one, introduced when you expose a run-time polymorphic interface to your users. Think of null pointers, memory management, shared data. None of these are easily solved in all cases (smart pointers are great, but even they are no silver bullet). In fact, from your post it doesn’t seem like you are trying to solve the problem of slicing, but rather sidestep it by not allowing users to make copies. All you need to do to offer a solution to the slicing problem, is add a virtual clone member function. I think the deeper problem with exposing a run-time polymorphic interface is that you force users to deal with reference semantics, which are harder to reason about than value semantics.

The best way that I know of to avoid these problems in C++ is to use type erasure. This is a technique where you hide a run-time polymorphic interface, behind a normal class interface. This normal class interface then has value semantics and takes care of all the polymorphic ‘mess’ behind the screens. std::function is a prime example of type erasure.

For a great explanation of why exposing inheritance to your users is bad and how type erasure can help to fix that see these presentations by Sean Parent:

Inheritance Is The Base Class of Evil (short version)

Value Semantics and Concepts-based Polymorphism (long version; easier to follow, but sound is not great)

You’re not paranoid. My first professional task as a C++ programmer resulted in slicing and crashing. I know of others. There are not a lot of good solutions for this.

Given your compiler constraints, option 2 is the best. Instead of making a macro, which your new programmers will view as weird and mysterious, I would suggest a script or tool for auto generating the code. If your new employees will be using an IDE, you should be able to create a “New MYCOMPANY Interface” tool that will ask for the interface name, and create the structure you are looking for.

If your programmers are using command line, then use whatever scripting language is available to you to create the NewMyCompanyInterface script to generate the code.

I have used this approach in the past for common code patterns (interfaces, state machines, etc). The nice part is that new programmers can read the output and understand it easily, reproducing the needed code when they need something that can’t be generated.

Macros and other meta-programming approaches tend to obfuscate what is happening, and the new programmers don’t learn what is happening ‘behind the curtain’. When they have to break the pattern, they are just as lost as before.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật