Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Partially complete types

11 views
Skip to first unread message

Vidar Hasfjord

unread,
Apr 14, 2008, 2:51:07 PM4/14/08
to
In C++ you are allowed to declare an incomplete type (a "forward").
Code can operate on references and pointers to such types. This is a
very nice feature with regard to encapsulation.

I often use this feature to omit detail in interfaces; detail that not
all clients need to know. For example:

class Component {
//...
public:
class Parameter;
virtual Parameter* get_parameter ();
};

Client code can use Component and even pass pointers to Parameters
around while still being blissfully unaware of Parameter's definition.
Clients that need to know can include a separate header where
Parameter is defined.

This works nicely except when the Parameter class really is private
and clients only should see an abstract interface, i.e.:

class Component {
public:
class IParameter {
public:
virtual string get_name () = 0;
virtual string get_value () = 0;
};
virtual IParameter* get_parameter ();
};

Unfortunately, now the implementation of Component and friends, helper
classes and free functions, no longer can use GetParameter without
doing a dynamic_cast should it be necessary to access Parameter
details. In other words, there is type erasure in the Component
interface.

It would be nice if it was possible to *partially* complete a type.
This would mean to specify the base (or bases) of a type without
providing the full definition. For example:

class Component {
public:
class IParameter {
//...
};
class Parameter : public IParameter; // partial type completion
virtual Parameter* get_parameter ();
};

Now the compiler can do a full type-check against what is known about
the Parameter type at any point in the code. The client code knows
only that Parameter inherits IParameter and is hence restricted to use
that interface, while the implementation code that has seen the full
Parameter definition can use all its methods and members; without need
for downcasting.

Any views on the feasability of such a feature?

Regards,
Vidar Hasfjord

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Alf P. Steinbach

unread,
Apr 14, 2008, 11:23:05 PM4/14/08
to
* Vidar Hasfjord:

> In C++ you are allowed to declare an incomplete type (a "forward").
> Code can operate on references and pointers to such types. This is a
> very nice feature with regard to encapsulation.
>
> I often use this feature to omit detail in interfaces; detail that not
> all clients need to know. For example:
>
> class Component {
> //...
> public:
> class Parameter;
> virtual Parameter* get_parameter ();
> };
>
> Client code can use Component and even pass pointers to Parameters
> around while still being blissfully unaware of Parameter's definition.
> Clients that need to know can include a separate header where
> Parameter is defined.
>
> This works nicely except when the Parameter class really is private
> and clients only should see an abstract interface, i.e.:
>
> class Component {
> public:
> class IParameter {
> public:
> virtual string get_name () = 0;
> virtual string get_value () = 0;
> };
> virtual IParameter* get_parameter ();
> };

Are you sure you really want to hand out pointers, and raw pointers at that?


> Unfortunately, now the implementation of Component and friends, helper
> classes and free functions, no longer can use GetParameter without
> doing a dynamic_cast should it be necessary to access Parameter
> details. In other words, there is type erasure in the Component
> interface.
>
> It would be nice if it was possible to *partially* complete a type.
> This would mean to specify the base (or bases) of a type without
> providing the full definition. For example:
>
> class Component {
> public:
> class IParameter {
> //...
> };
> class Parameter : public IParameter; // partial type completion
> virtual Parameter* get_parameter ();
> };
>
> Now the compiler can do a full type-check against what is known about
> the Parameter type at any point in the code. The client code knows
> only that Parameter inherits IParameter and is hence restricted to use
> that interface,

So from the client code's point of view a Parameter has the same functionality
as IParameter.

Why not then use IParameter?


> while the implementation code that has seen the full
> Parameter definition can use all its methods and members; without need
> for downcasting.
>
> Any views on the feasability of such a feature?

Cheers, & hth.,

- Alf

Greg Herlihy

unread,
Apr 14, 2008, 11:26:22 PM4/14/08
to
On Apr 14, 11:51 am, Vidar Hasfjord <vattilah-gro...@yahoo.co.uk>
wrote:

> In C++ you are allowed to declare an incomplete type (a "forward").
> Code can operate on references and pointers to such types. This is a
> very nice feature with regard to encapsulation.
>
> I often use this feature to omit detail in interfaces; detail that not
> all clients need to know. For example:
>
> class Component {
> //...
> public:
> class Parameter;
> virtual Parameter* get_parameter ();
> };
>
> Client code can use Component and even pass pointers to Parameters
> around while still being blissfully unaware of Parameter's definition.
> Clients that need to know can include a separate header where
> Parameter is defined.
>
> This works nicely except when the Parameter class really is private
> and clients only should see an abstract interface, i.e.:
>
> class Component {
> public:
> class IParameter {
> public:
> virtual string get_name () = 0;
> virtual string get_value () = 0;
> };
> virtual IParameter* get_parameter ();
> };

There is no reason to declare IParameter's virtual methods as pure -
because the library does in fact provide implementations of those
routines. Pure virtual methods necessitate that the client provide
implementations - but there is no such requirement in this case.
Instead, the Library interface should declare IParameter along these
lines:

class Component
{
public:
...
class IParameter
{
public:
virtual string get_name();
virtual string get_value();
};
virtual IParameter* get_parameter ();
};

The Library then internally defines Component and IParameter
subclasses:

// Internal Library Class Declarations

class ComponentImpl : public Component
{
class Parameter : public IParameter
{
public:
Parameter() : IParameter() {}
virtual ~Parameter() {}
// ...
};

virtual Parameter * get_parameter()
{
return new Parameter;
}
};

Essentially, the Library's factory method returns pointers to concrete
subtypes of the Library's publicly-declared interface classes. In this
way, the Library takes advantage of C++'s dynamic types and virtual
method dispatch to furnish implementations without exposing irrelevant
details.

> It would be nice if it was possible to *partially* complete a type.
> This would mean to specify the base (or bases) of a type without
> providing the full definition.

> ... The client code knows


> only that Parameter inherits IParameter and is hence restricted to use
> that interface, while the implementation code that has seen the full
> Parameter definition can use all its methods and members; without need
> for downcasting.

The solution is for the Library to make only the "Parameter" class
public - and to declare its "IParameter" subclass internally.

> Any views on the feasability of such a feature?

The C++ language already supports this behavior with virtual functions
and dynamic types.

Greg

Marcin Swiderski

unread,
Apr 16, 2008, 1:21:48 AM4/16/08
to
On 15 Kwi, 05:26, Greg Herlihy <gre...@mac.com> wrote:
>
> There is no reason to declare IParameter's virtual methods as pure -
> because the library does in fact provide implementations of those
> routines. Pure virtual methods necessitate that the client provide
> implementations - but there is no such requirement in this case.
>
Virtual method should be declared as pure if there is no
implementation
provided in class definition. IParameter is just an interface and as
such does not provide an implementation. If you won't declare it's
methods
as pure virtual, linker will fail.

Cheers
Sfider

Vidar Hasfjord

unread,
Apr 16, 2008, 5:57:21 AM4/16/08
to
On Apr 15, 4:26 am, Greg Herlihy <gre...@mac.com> wrote:
> Instead, the Library interface should declare IParameter along these
> lines:
> class Component
> [...]
> virtual IParameter* get_parameter ();
> [...]
> class ComponentImpl : public Component
> virtual Parameter* get_parameter ()

Yep, this will work due to covariant return types, which allows you to
strengthen the type info. But it is a lot of scaffolding.

A simpler solution is to just create two interfaces; one for external
use and one for internal use:

class Component {
public:
class IParameter {...};
virtual IParameter* get_parameter ();
private:
class Parameter;
Parameter* private_get_parameter ();
...
};

Implementation:

class Component::Parameter : Component::IParameter {
...
};
IParameter* Component::get_parameter ()
{return private_get_parameter ();}

This works and has less implications on the Component class hierarchy
compared to your solution. But it is still scaffolding. The interface
of Component is duplicated and one implemented in terms of the other.
It would be much more straight-forward to be able to tell the compiler
partial type information. Here's that hypothetical solution again:

class Component {
public:
class IParameter {...};


class Parameter : public IParameter; // partial type completion
virtual Parameter* get_parameter ();

private:
...
};

But is it possible/easy to implement? Does the compiler know enough
after seeing the partial type to use Parameter through the base class
IParameter?

Regards,
Vidar Hasfjord

Vidar Hasfjord

unread,
Apr 16, 2008, 5:58:31 AM4/16/08
to
On Apr 15, 4:23 am, "Alf P. Steinbach" <al...@start.no> wrote:
> Are you sure you really want to hand out pointers, and raw pointers at that?

The code examples are just for exposition. I avoided smart pointers in
the examples to keep it simple; also there are some dangerous
implications of using smart pointers with incomplete types (ref.
deleting an incomplete type). Since the post was about incomplete
types I tried to stay on safe ground.

> Why not then use IParameter?

See Greg's post and my follow-up.

Regards,
Vidar Hasfjord

0 new messages