C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

chunking class definitions ?

 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated)
View previous topic :: View next topic  
Author Message
Gabriel Zachmann
Guest





PostPosted: Thu Dec 11, 2003 11:41 am    Post subject: chunking class definitions ? Reply with quote



I have a frequently recurring problem, maybe some of you have a nice solution.

When I write a library, I like to bundle the API of that library in a small
number of classes. Let's assume, I need just one class X.
So I write a header file X.h containing:

class X
{
...
};

My goal is to write the whole library such that users of the lib need to
include *only* X.h .

Internally, the library will, of course, use many more classes, for which I
have several header files and c-files. Let's assume, one of the internally
needed classes is Y.

Unfortunately, quite often I will need one or more private member variables
in X of type Y (and Z, etc.), so in X.h I need to write, for instance:

#include <Y.h>
#include <Z.h>
class X
{
...
private:
Y y;
vector<Z> z;
};

What I really dislike about this is:
1. All of a sudden, I need to ship not only X.h with the library, but also
Y.h, Z.h, and all other header files those header files include.
2. I need to do this even though the private member variables in X are
*not* part of the interface of my library.

Of course, I could solve this along the 1-2 obvious ways
(i.e., turning all Y into Y*, or bundling them in a separate struct),
but I was wondering if there is anything nicer.

I took a quick look at cpptips, to no avail.

I would appreciate all suggestions and ideas.

Best regards,
Gabriel.

--
/-------------------------------------------------------------------------
Quote:
zach (AT) cs (DOT) uni-bonn.de __@/' [email]Gabriel.Zachmann (AT) gmx (DOT) net[/email] |
web.informatik.uni-bonn.de/~zach __@/' www.gabrielzachmann.org |
-------------------------------------------------------------------------/


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

Back to top
Hyman Rosen
Guest





PostPosted: Thu Dec 11, 2003 9:20 pm    Post subject: Re: chunking class definitions ? Reply with quote



You need to separate interface from implementation.
In X.h, do

struct X
{
static X *Create(...);
virtual ~X() = 0;
enum X_Constants { a, b, c };
struct api_type { int foo, bar; };
void api_func1(...);
int api_func2(...);
static double api_func3(...);
// etc.
};

Now your users will include X.h and call X::Create to get
an X object with which to work. In your private files, you
inherit your implementation class from X, and now you don't
have to reveal any of those details to users of X. Use some
smart pointer for Create if you want.


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Back to top
Joshua Lehrer
Guest





PostPosted: Thu Dec 11, 2003 9:27 pm    Post subject: Re: chunking class definitions ? Reply with quote



Gabriel Zachmann <zach (AT) cs (DOT) uni-bonn.de> wrote

Quote:
I have a frequently recurring problem, maybe some of you have a nice solution.

When I write a library, I like to bundle the API of that library in a small
number of classes. Let's assume, I need just one class X.
So I write a header file X.h containing:

class X
{
...
};

My goal is to write the whole library such that users of the lib need to
include *only* X.h .

Internally, the library will, of course, use many more classes, for which I
have several header files and c-files. Let's assume, one of the internally
needed classes is Y.


There are two common solutions.

1- Make class an interface, rather than a concrete base class. Then,
give your clients the ability to retrieve an X, rather than obtain one
via new:

struct X {
static std::auto_ptr<X> instantiate();
virtual ~X() { }

virtual void func() = 0;
virtual void func2() const = 0;
};

All of your "public" methods are virtual methods on X. In your
library, you implement X::instantiate, by returning a pointer to a
subclass, X2. X2 has all your data members, all of your private
implementation details, and implementations of the public virtual X
methods.

A huge benefit of this approach is that you can make "instantiate"
take arguments, and select different subclasses to implement based on
that argument or based on the client. For example, one client might
pay more for a faster implementation, or you may be able to pass in an
argument dictating if you want the English version of a dictionary or
the French one.


2- you the letter/envelope approach at a binary firewall:

struct X {
~X(); //destructor can not be inlined

void func1();
void func2() const;

private:
struct x_impl;
std::auto_ptr<x_impl> m_impl;
};

In this approach, you make all of your public methods non-virtual.
You have one private data member, which is a pointer to the
implementation class. The private class contains all of your data
members, private methods, and is not published to the consumer. The
public methods forward the work to the private class.

You may *think* this approach is faster because it has no virtual
functions. However, the forwarding of the public methods to the
private implementation class is almost the same thing. In fact, if
you think about it, the two approaches are almost identical, where the
former lets the compiler do what the latter does by hand.

joshua lehrer
factset research systems
NYSE:FDS

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

Back to top
Joshua Lehrer
Guest





PostPosted: Fri Dec 12, 2003 9:50 am    Post subject: Re: chunking class definitions ? Reply with quote

Hyman Rosen <hyrosen (AT) mail (DOT) com> wrote

Quote:
You need to separate interface from implementation.
In X.h, do

struct X
{
static X *Create(...);
virtual ~X() = 0;
enum X_Constants { a, b, c };
struct api_type { int foo, bar; };
void api_func1(...);
int api_func2(...);
static double api_func3(...);
// etc.
};


Don't the api_functions need to be virtual (and preferrably purely virtual)?

joshua lehrer
factset research systems
NYSE:FDS

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

Back to top
Michael Tiomkin
Guest





PostPosted: Sun Dec 14, 2003 2:02 am    Post subject: Re: chunking class definitions ? Reply with quote

[email]usenet_cpp (AT) lehrerfamily (DOT) com[/email] (Joshua Lehrer) wrote in message news:<31c49f0d.0312111944.134b95ea (AT) posting (DOT) google.com>...
Quote:
Hyman Rosen <hyrosen (AT) mail (DOT) com> wrote

You need to separate interface from implementation.
In X.h, do

struct X
{
static X *Create(...);
virtual ~X() = 0;
enum X_Constants { a, b, c };
struct api_type { int foo, bar; };
void api_func1(...);
int api_func2(...);
static double api_func3(...);
// etc.
};


Don't the api_functions need to be virtual (and preferrably purely virtual)?

No, they don't. Only the destructor has to be virtual. This interface
class would usually have only one implementation class that inherits
from it, and the API can be implemented in the base class.
BTW, the interface class can be much simpler with using a ptr,
as Gabriel had already said in his question:

class Xinterf
{
public:
Xinterf();
~Xinterf();
enum ....;
void api_...();
....
private:
class Xdata;
Xdata* data;
};

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

Back to top
Stefan Heinzmann
Guest





PostPosted: Mon Dec 15, 2003 9:48 pm    Post subject: Re: chunking class definitions ? Reply with quote

Joshua Lehrer wrote:
[... original library encasulation question snipped]
Quote:
There are two common solutions.

1- Make class an interface, rather than a concrete base class. Then,
give your clients the ability to retrieve an X, rather than obtain one
via new:

struct X {
static std::auto_ptr<X> instantiate();
virtual ~X() { }

virtual void func() = 0;
virtual void func2() const = 0;
};

All of your "public" methods are virtual methods on X. In your
library, you implement X::instantiate, by returning a pointer to a
subclass, X2. X2 has all your data members, all of your private
implementation details, and implementations of the public virtual X
methods.

A huge benefit of this approach is that you can make "instantiate"
take arguments, and select different subclasses to implement based on
that argument or based on the client. For example, one client might
pay more for a faster implementation, or you may be able to pass in an
argument dictating if you want the English version of a dictionary or
the French one.

I think that having a static function member instantiate() is most
appropriate for singletons, and when struct X actually encapsulates the
entire library or a big independent chunk of it. In other cases, it
might be better to have a singleton factory class whose purpose it is to
provide functions that return instances of the library's objects.

Furthermore, I think it would be good practice to disable copy
construction and copy assignment in the interface class. It is rather
unlikely that they can be implemented meaningfully, and as a library
writer you want to prevent mistakes the client code might accidentally make.

Quote:
2- you the letter/envelope approach at a binary firewall:

struct X {
~X(); //destructor can not be inlined

void func1();
void func2() const;

private:
struct x_impl;
std::auto_ptr<x_impl> m_impl;
};

In this approach, you make all of your public methods non-virtual.
You have one private data member, which is a pointer to the
implementation class. The private class contains all of your data
members, private methods, and is not published to the consumer. The
public methods forward the work to the private class.

Since the struct x_impl is defined in the corresponding .cpp file, I
tend to avoid function members in x_impl. Its data members can all be
public and accessed by struct X function members directly. This saves
you separate forwarding functions.

Quote:
You may *think* this approach is faster because it has no virtual
functions. However, the forwarding of the public methods to the
private implementation class is almost the same thing. In fact, if
you think about it, the two approaches are almost identical, where the
former lets the compiler do what the latter does by hand.

The comparison between the two approaches is actually more intricate and
deserves some discussion, because one could infer from your argument
that both are more or less equivalent and interchangeable. This is not so.

Calling member functions is about equally costly for both. Calling a
virtual function needs to look up the function in the vtable, which
takes a couple of instructions. The letter/envelope idiom avoids that
but adds a level of indirection to fetch the data pointer.

The letter/envelope idiom has a big advantage over the interface idiom:
It is a concrete class of which instances can be created directly. This
is particularly important when the objects have to behave like values,
that is: Can be copied, passed by value, held in containers, etc.
Copying the envelope in this case also copies the letter. Ironically, in
such cases the auto_ptr is the wrong tool in the envelope class, because
it has the wrong copy semantics. Something like boost smart pointers
would be more appropriate if you don't want to use a plain pointer and
do the management yourself.

To compensate for this, it has also a big disadvantage: The hidden data
object needs to be allocated with new (in the constructors of struct X)
and deleted (in the destructor of struct X). Thus, object creation and
deletion is limited by the efficiency of dynamic memory management,
although your users don't see this from the outside.

To be fair, the interface idiom does usually have the same limitation,
because the factories will usually also invoke dynamic memory management.

The interface idiom has the additional advantage that there can be
several different implementations for different purposes without your
clients noticing it.

In a nutshell, I usually use letter/envelope when I need value behaviour
for my class and interfaces otherwise, in other words interfaces are the
general case and letter/envelope the exception.

Note also that through RTTI, a client can gain slightly more information
in the case of interfaces than for the letter/envelope idiom. For
example, it is possible to compare the dynamic type of two instances of
your interface class to see whether they are the same. So you could
argue that letter/envelope encapsulates better.

There is actually a third variation on the theme: The handle. This is
similar to letter/envelope in that the handle class contains a pointer
to the actual hidden data representation. However, copying the handle
does not copy the data. The handle is behaving like a smart reference,
but you can't get at the object it refers to, you can only manipulate
the object's state by calling the handle's function members.

This means that you should support (encourage?) to pass the handle by
value. You need copy construction and copy assignment. Behind the
scenes, you will have to implement a resource tracking scheme so that
you can get rid of the data representation when appropriate. The handle
idiom is much used in C code, where the handle often is just an opaque
pointer. Wrapping the opaque pointer in a proper class in C++ gives you
more control over resource allocation/deallocation, but otherwise
achieves the same thing.

Cheers
Stefan


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

Back to top
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.