Implicit vs explicit interfaces

What are the pros and cons of using implicit interfaces (examples 2 and 3, templates) versus using explicit interfaces (example 1, a pointer to an abstract class) in the following example?

Code that does not change:

class CoolClass { public: virtual void doSomethingCool() = 0; virtual void worthless() = 0; }; class CoolA : public CoolClass { public: virtual void doSomethingCool() { /* Do cool stuff that an A would do */ } virtual void worthless() { /* Worthless, but must be implemented */ } }; class CoolB : public CoolClass { public: virtual void doSomethingCool() { /* Do cool stuff that a B would do */ } virtual void worthless() { /* Worthless, but must be implemented */ } }; 

Case 1: a non-templated class that accepts a pointer to a base class that provides an explicit interface:

 class CoolClassUser { public: void useCoolClass(CoolClass * coolClass) { coolClass.doSomethingCool(); } }; int main() { CoolClass * c1 = new CoolA; CoolClass * c2 = new CoolB; CoolClassUser user; user.useCoolClass(c1); user.useCoolClass(c2); return 0; } 

Case 2: a template class whose template type provides an implicit interface:

 template <typename T> class CoolClassUser { public: void useCoolClass(T * coolClass) { coolClass->doSomethingCool(); } }; int main() { CoolClass * c1 = new CoolA; CoolClass * c2 = new CoolB; CoolClassUser<CoolClass> user; user.useCoolClass(c1); user.useCoolClass(c2); return 0; } 

Case 3: a template class whose template type provides an implicit interface (this time, not based on CoolClass :

 class RandomClass { public: void doSomethingCool() { /* Do cool stuff that a RandomClass would do */ } // I don't have to implement worthless()! Na na na na na! }; template <typename T> class CoolClassUser { public: void useCoolClass(T * coolClass) { coolClass->doSomethingCool(); } }; int main() { RandomClass * c1 = new RandomClass; RandomClass * c2 = new RandomClass; CoolClassUser<RandomClass> user; user.useCoolClass(c1); user.useCoolClass(c2); return 0; } 

Case 1 requires that the object passed to useCoolClass () be a child of CoolClass (and implement a worthless () ). On the other hand, cases 2 and 3 will take the any class, which has the doSomethingCool () function.

If users of the code have always had cool CoolClass subclasses, then case 1 makes intuitive sense, since CoolClassUser will always wait for the CoolClass implementation. But suppose this code will be part of the API structure, so I cannot predict whether users will want to subclass CoolClass or collapse their own class, which has the doSomethingCool () function.

Some related posts:

stack overflow

stack overflow

stack overflow

+7
source share
2 answers

Some considerations that came to my mind why you might prefer case 1:

  • If CoolClass not a clean interface, that is, part of the implementation is also inherited (although you can also provide it for case 2/3, for example, as a base class);
  • if there are reasons for CoolClassUser to CoolClassUser implemented in binary, and not in the header (and this is not only protection, but it can also be code size, resource management, centralized error handling, etc.);
  • if you want to save pointers and use them later, then also example 1 looks better: (a) it is easier for him to store everything in one container and (b) you will need to save the actual data type as for case 2/3 the solution that comes to the mind is to transform it into an “explicit” interface (for example, case 1) using a template shell.

Reasons why a 2/3 case may be preferable:

  • if later you decide that worthless() now worth something, and start using it, in case 2 you get compile-time errors for classes where it is not implemented. In case 1, nothing will remind you to implement these functions for real, with the possible exception of errors at runtime if you are (un) lucky.
  • Case2 / 3 may have slightly better performance, although at the expense of larger code.

In some cases, this may be solely a matter of personal preference, your or your users.

+3
source

Keep in mind that in cases No. 2 and No. 3 you depend on the template parameters, which means that the encoder must correctly create the template argument with the correct type during the call. Depending on how the functions are used, this can create some problems in which you want to create an abstract interface for the user without worrying about the type of object being transferred ... i.e. A "handle" or some other pointer to a derived object that uses polymorphism to pass an object from one API function to another. For example:

 class abstract_base_class; abtract_base_class* get_handle(); void do_something_with_handle(abstract_base_class* handle); void do_something_else_with_handle(abstract_base_class* handle); //... more API functions 

Now your API infrastructure can pass the object back to the user of your code, and they don’t need to know what this object is ... they only need to know that it describes some kind of interface, which you can, of course, publicly expose in the header. But they don’t need to know anything about the “guts” of the object that you gave them. You can give them a pointer to some derived type that you control. You will only need to provide templates for the most common types of functions in your API. Otherwise, the need to create a template for functions that are only intended to be abstract_base_class* just makes it more boilerplate for user input.

+1
source

All Articles