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.
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();
////////////////// 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;
}
CDPlayer* p = CreateCDPLayer();
Play(p);
Skip(p);
Stop(p);
Eject(p);
Destroy(p);
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
A common approach in C++ is to write a CDPlayer class definition in a public header
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.
A common way to avoid exposing the implementation in a public header is to define a pure abstact base class:
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.
This is a forward declaration of a
Note that a forward declaration doesn't use the public modifier (
This is a definition of a
This is an implementation of a
There can be any number of forward declarations.
There must be exactly one definition and exactly one implementation of a
It is typically appropriate to use the C++
In the above example the directives
A
Also note that any number of base classes may be specified. This is just normal C++ subclassing.
template
Approach 1 : CDPlayer class in public header
////////////////// CDPlayer.h //////////////////
class CDPlayer
{
public:
CDPlayer();
~CDPlayer();
void Eject();
void Play();
void Stop();
void Skip();
private:
...
};
Approach 2 : Define a pure abstact base class in a public header
////////////////// 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();
Forward declaration versus definition versus implementation of $adt
$adt
:
$adt X;
+
).
If you do you will get warning W1017: + on forward declaration of X deprecated
$adt
:
$adt+ X
{
};
$adt
:
$adt+ implement X
{
};
$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() {}
};
final
keyword when implementing a
$adt
to indicate that the implementation class cannot be subclassed.
-dc
(no default constructor)
and -copy
(no copy assignment and no copy constructor) are specified on the implementation.
This affects what functions are reflected.
$adt
can declare that it implements interfaces using the isa
keyword.