$adt

A type is a set of values plus a set of operators on those values. The only way to manipulate values is through the operations that are defined on them. This allows a client to be ignorant of how the type has been implemented. To emphasise this, the literature uses the term Abstract Data Type (ADT). Some authors like Chris Date have reasonably argued that this terminology is superfluous because by definition every type is an ADT.

In any case, it is convenient to use the term ADT to refer to something quite specific : The old C programming idiom of forward declaring a struct in a header, together with a set of (global) functions that take pointers to the forward declared struct.

With some further abuse of terminology, it is convenient to use the term for a type of state machine (rather than a type of value), for which the operations on the state machine have been abstracted away from the implementation.

For example


////////////////// CDPlayer.h //////////////////

struct CDPlayer;

CDPlayer* CreateCDPLayer();
void Destroy(CDPlayer*);

void Eject(CDPlayer*);
void Play(CDPlayer*);
void Stop(CDPlayer*);
void Skip(CDPlayer*);

This is an excellent idiom. It is simple and convenient, very efficient and completely hides the implementation. It achieves the ultimate in binary decoupling.

Special syntax to support ADTs

The Xc++ language provides a syntactically convenient way of writing the above code, as follows


////////////////// CDPlayer.h //////////////////

$adt+ CDPlayer
{
    void Eject();
    void Play();
    void Stop();
    void Skip();
    void Destroy();
};

$function+ CDPlayer* CreateCDPLayer();

Implementation of an ADT


////////////////// CDPlayer.cpp //////////////////

$adt+ implement CDPlayer
{
    void Eject() { ... }
    void Play() { ... }
    void Stop() { ... }
    void Skip() { ... }
    void Destroy() { delete this; }

    /////// member variables ////////
    ...
};

$function+ CDPlayer* CreateCDPLayer()
{
    return new CDPlayer;
}

Access by a client


CDPlayer* p = CreateCDPLayer();
Play(p);
Skip(p);
Stop(p);
Eject(p);
Destroy(p);

Function overloading

C++ allows for name overloading, therefore it is reasonable to use simple names for global functions that represent operations on an ADT. Simple naming conventions allow for code reuse through templates - such as the following AutoDestroy class


template
class AutoDestroy
{
public:
    AutoDestroy(T* p) : p_(p) {}
    ~AutoDestroy() { Destroy(p_); }

private:
    T* p_;
};

Approach 1 : CDPlayer class in public header

A common approach in C++ is to write a CDPlayer class definition in a public header


////////////////// CDPlayer.h //////////////////
class CDPlayer
{
public:
    CDPlayer();
    ~CDPlayer();
    void Eject();
    void Play();
    void Stop();
    void Skip();

private:
    ...
};

Although the implementation would normally be "private", it leads to binary coupling. This approach is particularly suitable for simple types like Point and Circle because C++ allows for these types to be used as "value types" and the operations can be fully inlined.

For a more complex type like a CDPlayer it can be desirable to avoid exposing the implementation in a public header. Reasons include

Binary coupling can be particularly serious if it for example exposes an implementation using standard library classes like std::vector and std::map because the these classes don't represent a binary standard for interoperability.

Approach 2 : Define a pure abstact base class in a public header

A common way to avoid exposing the implementation in a public header is to define a pure abstact base class:


////////////////// CDPlayer.h //////////////////

struct ICDPlayer
{
    virtual void Eject() = 0;
    virtual void Play() = 0;
    virtual void Stop() = 0;
    virtual void Skip() = 0;
    virtual void Destroy() = 0;
};

ICDPlayer* CreateCDPLayer();

This approach is practical and works well. Ignoring simple syntactic differences, the following are some pros and cons compared to the C style of writing an ADT.

Pros of abstract interface

Cons of abstract interface

When a $adt is reflected an instance of a ReflectedClass is registered in the ReflectedClass registry.

Forward declaration versus definition versus implementation of $adt

This is a forward declaration of a $adt:


$adt X;

Note that a forward declaration doesn't use the public modifier (+). If you do you will get warning W1017: + on forward declaration of X deprecated

This is a definition of a $adt:


$adt+ X
{
};

This is an implementation of a $adt:


$adt+ implement X
{
};

There can be any number of forward declarations. There must be exactly one definition and exactly one implementation of a $adt.

Other features available when implement a $adt


// X.h
$adt+ X
{
    void f();
};

// X.cpp
$adt+ implement X final <<-dc -copy>> isa IObject : Base1, Base2, private Base3
{
    void f() {}
};

It is typically appropriate to use the C++ final keyword when implementing a $adt to indicate that the implementation class cannot be subclassed.

In the above example the directives -dc (no default constructor) and -copy (no copy assignment and no copy constructor) are specified on the implementation. This affects what functions are reflected.

A $adt can declare that it implements interfaces using the isa keyword.

Also note that any number of base classes may be specified. This is just normal C++ subclassing.