Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Interfaces

Last updated Jan 1, 2003.

Multiple inheritance in C++ has two flavors, namely interface inheritance and implementation inheritance. Today, I will explain the notion of interfaces, and present the syntactic cues that distinguish them from concrete classes.

Interfaces Explained

A class's interface consists of its public members. Usually, these would be member functions, although in certain rare cases, local data types and even data members may also be included. By contrast, the non-public members of a class are said to be its implementation. For example:

class Date
{
public: //Date's interface appears in bold
 Date();
 int getCurrentDate(string & datestr) const;
 Date operator +(const Date & d) const;
private: //Date's implementation
 int day, month, year;
 bool daylight_saving;
};

Although Date has an interface, the class itself is not an interface because it contains non-public members. By contrast, the following class is an interface:

class File
{
public: 
 explicit File(const string& name, int mode=0x666);
 virtual int Open();
 virtual int Close();
 virtual ~File();
};

Notice that every member function in File is both public and virtual (except for the constructor, of course). These syntactic cues suggest that File should serve only as a base for other, more specialized classes. Certain frameworks mark this attribute explicitly, by using the keyword struct instead of class. As you already know, the only difference between the two in C++ is that struct has public access by default, whereas class entails private access. Therefore, the previous example could be rewritten like this:

struct File //explicitly indicates that this is an interface
{
 explicit File(const string& name, int mode=0x666);
 virtual int Open();
 virtual int Close();
 virtual ~File();
};

Another characteristic of interfaces is that they merely declare member functions. The classes derived from them should define, or implement these functions. The use of virtual member functions isn't sufficient for this purpose, because a derived class isn't forced to override any of the base class's member functions. Furthermore, the author of class File must provide a default implementation for its members anyway, or else the linker will complain. Fortunately, there's a better way to achieve this goal.

Abstract Classes

Unlike an ordinary virtual function, a pure virtual function doesn't have to be defined. It can be declared in the class body without an implementation. C++ uses a special syntactic form to mark pure virtual functions by adding the pure specifier =0; to a declaration of such a function. For example:

struct File 
{
 explicit File(const string& name, int mode=0x666);
 virtual int Open()=0; //pure virtual
 virtual int Close()=0; //ditto
 virtual ~File()=0; //a dtor may be pure virtual too
};

A class that has at least one pure virtual function is called an abstract class. An abstract class is more or less synonymous with our notion of an interface.

At last, we've turned File into a proper interface. Notice that the use of the pure specifier ensures two important things. First, it guarantees that users cannot instantiate an object of an abstract class:

File file("data.txt"); //compilation error: File is an abstract class

You may, however, define pointers and references to an abstract class to achieve polymorphic behavior:

File *pfile; //OK, pfile should point to a derived object
void func(File &ref);

C++ ensures that classes derived from an abstract class also obey this rule. You may instantiate objects of a class derived from an abstract class only if it implements all the pure virtual functions it inherited from the base class(es).

Combining Multiple Interfaces

You may combine several interfaces in a single concrete class. For example, suppose you want to define a class that represents a file stored on removable media. You can combine two interfaces like this:

//an interface the represents an abstract removable device
struct RemovableMedia
{
 virtual int Explore()=0; //view contents 
 virtual int Save(const string& file)=0;
 virtual size_t Retrieve(const string& file, void* buf)=0;
};
//public inheritance implied from 'struct':
class RemovableFile: RemovableMedia, File 
{
//...implement all the pure virtual member functions
};

You may combine more interfaces to create a more specialized object:

class RemovableFile: RemovableMedia, 
           File, 
           Encrypted
{
//...implement all the pure virtual member functions
};

Remember that the interfaces are all publicly inherited because of the struct keyword. However, to be more explicit about this (and to be on the safe side, should the interfaces' author decide to use class instead of struct), you may use the public keyword:

class RemovableFile: public RemovableMedia, 
           public File, 
           public Encrypted
{
 //...implementation
};

The DDD Strikes Again

The fact that interfaces don't have data members still doesn't prevent ambiguities. If, for example, one of the interfaces is inherited multiple times throughout the derivation lattice, you're in trouble:

class TextFile: public File, public Encrypted
{};
class NetworkFile: public File, public Encrypted
{};
class DataFile: public TextFile, public NetworkFile //DDD!
{};
File *pf = new DataFile;//error; ambiguous base class

DataFile happens to have two copies of File and Encrypted. To avoid this, use virtual inheritance:

class TextFile: public virtual File, 
        public virtual Encrypted
{};
class NetworkFile: public virtual File, 
          public virtual Encrypted
{};
class DataFile: public virtual TextFile, 
        public virtual NetworkFile //now fine
{};

Pure Virtual Functions: Final Refinements

Generally speaking, a pure virtual function needn't be implemented in an abstract class, nor should it be. However, in one special case, namely a pure virtual destructor, it must have an implementation. The reason is that a destructor of a derived object automatically invokes its base class' destructor, recursively. Eventually, the abstract class's destructor is called too. If it has no implementation, you'll get a runtime crash. Therefore, it's important to provide a definition for every pure virtual destructor:

struct File 
{
 explicit File(const string& name, int mode=0x666);
 virtual int Open()=0;
 virtual int Close()=0;
 virtual ~File()=0; //must have a definition
};
File::~File() {} //dummy implementation 

In all other cases, defining a pure virtual function is optional (and rather unusual!).

Summary

The use of pure virtual functions enables you to specify only an interface, without an implementation. As a rule of thumb, an interface class should be compact, containing no more than five pure virtual functions. This way, you may define multiple specialized interfaces that a derived class can combine as needed. Lumping dozens of pure virtual functions in a single fat interface is a design mistake because derived classes will be forced to implement all of these functions, even if they intend to use only a small portion of them.