[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

Proper use of templated containers as class members

Per

12/4/2008 10:15:00 AM

I am finding myself doing the following rather often when I have
template containers in classed, e.g. stl::map

For example:

class Foo
{
public:
typedef std::map<std::string, int> direcory_t;
//More typedefs of the same style...
private:
directory_t directory_m;
//More members of the same style...
public:
//Constructors and stuff
const directory_t& directory() const {return directory_m;}
};

The question is what is your opinion on typedefing like this. At the
same time as it saves a lot of typing. After a while there are very
many types in a project. And in the end can get confusing about what
the type really contains. Classes should of course hide their
implementation but it seams very stupid not to have read access
directly to a member. If I didn't have that I have to bloat the class
with wrapper functions for the members. And not using typedefs would
lead to an awful lot of typing.
18 Answers

Victor Bazarov

12/4/2008 3:16:00 PM

0

Per wrote:
> I am finding myself doing the following rather often when I have
> template containers in classed, e.g. stl::map
>
> For example:
>
> class Foo
> {
> public:
> typedef std::map<std::string, int> direcory_t;
> //More typedefs of the same style...
> private:
> directory_t directory_m;
> //More members of the same style...
> public:
> //Constructors and stuff
> const directory_t& directory() const {return directory_m;}
> };
>
> The question is what is your opinion on typedefing like this. At the
> same time as it saves a lot of typing. After a while there are very
> many types in a project. And in the end can get confusing about what
> the type really contains. Classes should of course hide their
> implementation but it seams very stupid not to have read access
> directly to a member. If I didn't have that I have to bloat the class
> with wrapper functions for the members. And not using typedefs would
> lead to an awful lot of typing.

I use this approach everywhere. It's an abstraction. The users of your
class will use "Foo::directory_t" when they need the type. The main
thing is not saving typing (although it's a nice side effect) but the
ability to change what 'directory_t' means with a single line, and not
to worry about changing the rest of the code that uses it. That's the
whole idea behind typedefs. It doesn't matter how they are defined, as
a namespace member or as a class member.

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask

Maxim Yegorushkin

12/4/2008 3:39:00 PM

0

On Dec 4, 3:15 pm, Victor Bazarov <v.Abaza...@comAcast.net> wrote:
> Per wrote:
> > I am finding myself doing the following rather often when I have
> > template containers in classed, e.g. stl::map
>
> > For example:
>
> > class Foo
> > {
> > public:
> >    typedef std::map<std::string, int> direcory_t;
> >    //More typedefs of the same style...
> > private:
> >    directory_t directory_m;
> >    //More members of the same style...
> > public:
> >    //Constructors and stuff
> >    const directory_t& directory() const {return directory_m;}
> > };
>
> > The question is what is your opinion on typedefing like this. At the
> > same time as it saves a lot of typing. After a while there are very
> > many types in a project. And in the end can get confusing about what
> > the type really contains. Classes should of course hide their
> > implementation but it seams very stupid not to have read access
> > directly to a member. If I didn't have that I have to bloat the class
> > with wrapper functions for the members. And not using typedefs would
> > lead to an awful lot of typing.
>
> I use this approach everywhere.  It's an abstraction.  The users of your
> class will use "Foo::directory_t" when they need the type.  The main
> thing is not saving typing (although it's a nice side effect) but the
> ability to change what 'directory_t' means with a single line, and not
> to worry about changing the rest of the code that uses it.  That's the
> whole idea behind typedefs.  It doesn't matter how they are defined, as
> a namespace member or as a class member.

More often such an approach is called abstraction leak.

The interface of directory_t "abstraction" is that of std::map<>. You
can only change the type of directory_t to something that supports the
full interface of std::map<>, otherwise you break the client code (you
may be lucky if there was limited use of std::map<> directory_t
interface, so that recompilation after changing the type of
directory_t succeeds, but you can't count on that).

--
Max

Jeff Schwab

12/4/2008 4:29:00 PM

0

Maxim Yegorushkin wrote:
> On Dec 4, 3:15 pm, Victor Bazarov <v.Abaza...@comAcast.net> wrote:
>> Per wrote:
>>> I am finding myself doing the following rather often when I have
>>> template containers in classed, e.g. stl::map
>>> For example:
>>> class Foo
>>> {
>>> public:
>>> typedef std::map<std::string, int> direcory_t;
>>> //More typedefs of the same style...
>>> private:
>>> directory_t directory_m;
>>> //More members of the same style...
>>> public:
>>> //Constructors and stuff
>>> const directory_t& directory() const {return directory_m;}
>>> };
>>> The question is what is your opinion on typedefing like this. At the
>>> same time as it saves a lot of typing. After a while there are very
>>> many types in a project. And in the end can get confusing about what
>>> the type really contains. Classes should of course hide their
>>> implementation but it seams very stupid not to have read access
>>> directly to a member. If I didn't have that I have to bloat the class
>>> with wrapper functions for the members. And not using typedefs would
>>> lead to an awful lot of typing.
>> I use this approach everywhere. It's an abstraction. The users of your
>> class will use "Foo::directory_t" when they need the type. The main
>> thing is not saving typing (although it's a nice side effect) but the
>> ability to change what 'directory_t' means with a single line, and not
>> to worry about changing the rest of the code that uses it. That's the
>> whole idea behind typedefs. It doesn't matter how they are defined, as
>> a namespace member or as a class member.

I also use this technique quite a bit, though usually with iterators,
rather than container types.

> More often such an approach is called abstraction leak.

There is the potential for leakage under certain circumstances, but
there is no abstraction leak in the posted code. For you to list any
such problem with the code, you're going to have to start with "what if..."

> The interface of directory_t "abstraction" is that of std::map<>. You
> can only change the type of directory_t to something that supports the
> full interface of std::map<>, otherwise you break the client code (you
> may be lucky if there was limited use of std::map<> directory_t
> interface, so that recompilation after changing the type of
> directory_t succeeds, but you can't count on that).

The problem you're describing applies when the amount of client code
that can access the exposed type is unbounded. When the code is already
encapsulated within some intermediate layer, e.g. it is an
implementation detail of some stand-alone application, there is no need
to enforce any tighter enapsulation. The public typedef for the
otherwise private type is worth having in such a scenario, not because
it limits potential uses of the type, but because it serves to identify
them.

The literal text "std::map<std::string,int> tells the reader what type
is in use, but Foo::directory_t gives the more relevant information of
why this is the right type for the job. If ever you do need to change
the type's interface, it's a lot easier to search for Foo::directory_t
than to figure out which instances of std::map<std::string,int> are
relevant to Foo.

If you break the interface in a way that the compiler catches, within
the context of implementing an application, it's not a particularly big
deal. The potential to break the interface in ways the compiler cannot
catch, e.g. changing the big-O complexity of an operation, is no less
prevalent if a custom type is used rather than a typedef.

Maxim Yegorushkin

12/4/2008 4:38:00 PM

0

On Dec 4, 4:29 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
> Maxim Yegorushkin wrote:
> > On Dec 4, 3:15 pm, Victor Bazarov <v.Abaza...@comAcast.net> wrote:
> >> Per wrote:
> >>> I am finding myself doing the following rather often when I have
> >>> template containers in classed, e.g. stl::map
> >>> For example:
> >>> class Foo
> >>> {
> >>> public:
> >>>    typedef std::map<std::string, int> direcory_t;
> >>>    //More typedefs of the same style...
> >>> private:
> >>>    directory_t directory_m;
> >>>    //More members of the same style...
> >>> public:
> >>>    //Constructors and stuff
> >>>    const directory_t& directory() const {return directory_m;}
> >>> };
> >>> The question is what is your opinion on typedefing like this. At the
> >>> same time as it saves a lot of typing. After a while there are very
> >>> many types in a project. And in the end can get confusing about what
> >>> the type really contains. Classes should of course hide their
> >>> implementation but it seams very stupid not to have read access
> >>> directly to a member. If I didn't have that I have to bloat the class
> >>> with wrapper functions for the members. And not using typedefs would
> >>> lead to an awful lot of typing.
> >> I use this approach everywhere.  It's an abstraction.  The users of your
> >> class will use "Foo::directory_t" when they need the type.  The main
> >> thing is not saving typing (although it's a nice side effect) but the
> >> ability to change what 'directory_t' means with a single line, and not
> >> to worry about changing the rest of the code that uses it.  That's the
> >> whole idea behind typedefs.  It doesn't matter how they are defined, as
> >> a namespace member or as a class member.
>
> I also use this technique quite a bit, though usually with iterators,
> rather than container types.
>
> > More often such an approach is called abstraction leak.
>
> There is the potential for leakage under certain circumstances, but
> there is no abstraction leak in the posted code.  For you to list any
> such problem with the code, you're going to have to start with "what if...."
>
> > The interface of directory_t "abstraction" is that of std::map<>. You
> > can only change the type of directory_t to something that supports the
> > full interface of std::map<>, otherwise you break the client code (you
> > may be lucky if there was limited use of std::map<> directory_t
> > interface, so that recompilation after changing the type of
> > directory_t succeeds, but you can't count on that).
>
> The problem you're describing applies when the amount of client code
> that can access the exposed type is unbounded.  When the code is already
> encapsulated within some intermediate layer, e.g. it is an
> implementation detail of some stand-alone application, there is no need
> to enforce any tighter enapsulation.  The public typedef for the
> otherwise private type is worth having in such a scenario, not because
> it limits potential uses of the type, but because it serves to identify
> them.
>
> The literal text "std::map<std::string,int> tells the reader what type
> is in use, but Foo::directory_t gives the more relevant information of
> why this is the right type for the job.  If ever you do need to change
> the type's interface, it's a lot easier to search for Foo::directory_t
> than to figure out which instances of std::map<std::string,int> are
> relevant to Foo.
>
> If you break the interface in a way that the compiler catches, within
> the context of implementing an application, it's not a particularly big
> deal.  The potential to break the interface in ways the compiler cannot
> catch, e.g. changing the big-O complexity of an operation, is no less
> prevalent if a custom type is used rather than a typedef.

My point was that it was not an abstraction, because it abstracts away
nothing but the name of the type. Rather a convenience typedef to make
code less fragile.

Sorry for nor being clear on this.

--
Max

Jeff Schwab

12/4/2008 4:46:00 PM

0

Maxim Yegorushkin wrote:
> On Dec 4, 4:29 pm, Jeff Schwab <j...@schwabcenter.com> wrote:
>> Maxim Yegorushkin wrote:
>>> On Dec 4, 3:15 pm, Victor Bazarov <v.Abaza...@comAcast.net> wrote:
>>>> Per wrote:

>>>>> class Foo
>>>>> {
>>>>> public:
>>>>> typedef std::map<std::string, int> direcory_t;
....
>>>>> };
>>>>> The question is what is your opinion on typedefing like this.

>>>> I use this approach everywhere. It's an abstraction.

>>> More often such an approach is called abstraction leak.

>>> The interface of directory_t "abstraction" is that of std::map<>. You
>>> can only change the type of directory_t to something that supports the
>>> full interface of std::map<>, otherwise you break the client code

>> The public typedef for the
>> otherwise private type is worth having [...], not because
>> it limits potential uses of the type, but because it serves to identify
>> them.

> My point was that it was not an abstraction, because it abstracts away
> nothing but the name of the type. Rather a convenience typedef to make
> code less fragile.

"Nothing but?" Abstracting the name of the type is a big deal. When I
write Java code, typedef is the single feature I miss most, for exactly
this reason. When I write std::vector<int>::iterator, it's not just
convenient; it's because I really don't know what the underlying type
is. That's abstraction at its best.

> Sorry for nor being clear on this.

We're entitled to differing opinions. :)

Per

12/4/2008 5:32:00 PM

0

On 2008-12-04, Puppet_Sock <puppet_sock@hotmail.com> wrote:
> On Dec 4, 5:15 am, Per <p...@isy.liu.se> wrote:
>> I am finding myself doing the following rather often when I have
>> template containers in classed, e.g. stl::map
>>
>> For example:
>>
>> class Foo
>> {
>> public:
>>    typedef std::map<std::string, int> direcory_t;
>>    //More typedefs of the same style...
>> private:
>>    directory_t directory_m;
>>    //More members of the same style...
>> public:
>>    //Constructors and stuff
>>    const directory_t& directory() const {return directory_m;}
>>
>> };
>>
>> The question is what is your opinion on typedefing like this. At the
>> same time as it saves a lot of typing. After a while there are very
>> many types in a project. And in the end can get confusing about what
>> the type really contains. Classes should of course hide their
>> implementation but it seams very stupid not to have read access
>> directly to a member. If I didn't have that I have to bloat the class
>> with wrapper functions for the members. And not using typedefs would
>> lead to an awful lot of typing.
>
> Think of the class Foo as an exporter of some service.
>
> Is that service just "work as a holder of a member of
> type directory_t"? Sometimes that is appropriate.
> It is certainly not a good idea to prohibit such things
> as sometimes such a simple "bag of data" is very useful.
>
> But maybe what should happen is that you refactor a
> bit and so keep all knowledge of the directory_t
> type object inside the class Foo. Maybe the code
> that needs to know that such a type, and a member
> of that type, exists, maybe all that code should be
> inside the class Foo. (Or possibly a very tightly
> related small collection of classes. And then you
> probably want to look up the proper uses of friend.)

Hmm yes thats a thought, but I don't always know what the users of the
class is beforehand since its currently an evolving research project.
Often an stl container or two is a good structure for what service the
class provides, plus some extra features. So hiding types would lead
to an awful lot of extra work reimplementing the interface of the
container. And using the friend approach im not comfortable with
either, this would mean some classes needs to know about other classes
and thus not getting a clear separation between the classes.

Of course I try to hide as much of the representation as possible. Im
not completely new to OO-design, perhaps this is why this approach has
been nagging me and hence the question here.

> That way, the class Foo could become an exporter of
> a higher level service such as (from the name
> "directory") "look after all the tasks required
> to deal with a telephone directory". (Or whatever
> it's a directory of.) Then the typedef is not used
> outside the class.
> Socks

Well if it was as easy as a telephone directory :) things would be
pretty clear-cut. But my project is a bit more complicated than that ;)


Thanks for all your thoughts so far.

Puppet_Sock

12/4/2008 8:00:00 PM

0

On Dec 4, 5:15 am, Per <p...@isy.liu.se> wrote:
> I am finding myself doing the following rather often when I have
> template containers in classed, e.g. stl::map
>
> For example:
>
> class Foo
> {
> public:
>    typedef std::map<std::string, int> direcory_t;
>    //More typedefs of the same style...
> private:
>    directory_t directory_m;
>    //More members of the same style...
> public:
>    //Constructors and stuff
>    const directory_t& directory() const {return directory_m;}
>
> };
>
> The question is what is your opinion on typedefing like this. At the
> same time as it saves a lot of typing. After a while there are very
> many types in a project. And in the end can get confusing about what
> the type really contains. Classes should of course hide their
> implementation but it seams very stupid not to have read access
> directly to a member. If I didn't have that I have to bloat the class
> with wrapper functions for the members. And not using typedefs would
> lead to an awful lot of typing.

Victor's advice is excellent, as usual.

Maxim and Jeff discussing this produced a thought.
Do you in fact need to expose this through the
interface of the class? Essentially what you have
there is a "getter" function.

const directory_t& directory() const {return directory_m;}

This exposes that the class contains something of
type directory_t, and so needs to expose the type also.
In principle, it exposes that there is a data member
of that type. (Though, in general, it only implies that
the class can proffer up data in that form. Recall the
evergreen discussions about a point class that can
provide x-y data or r-theta data.)

Usually what this means is that you have a fairly
thin abstraction in the class. That is, you've let
some of the functionality to do with the directory_t
type members get outside the class.

Think of the class Foo as an exporter of some service.

Is that service just "work as a holder of a member of
type directory_t"? Sometimes that is appropriate.
It is certainly not a good idea to prohibit such things
as sometimes such a simple "bag of data" is very useful.

But maybe what should happen is that you refactor a
bit and so keep all knowledge of the directory_t
type object inside the class Foo. Maybe the code
that needs to know that such a type, and a member
of that type, exists, maybe all that code should be
inside the class Foo. (Or possibly a very tightly
related small collection of classes. And then you
probably want to look up the proper uses of friend.)

That way, the class Foo could become an exporter of
a higher level service such as (from the name
"directory") "look after all the tasks required
to deal with a telephone directory". (Or whatever
it's a directory of.) Then the typedef is not used
outside the class.
Socks

ma740988

12/5/2008 2:36:00 AM

0

On Dec 4, 5:15 am, Per <p...@isy.liu.se> wrote:

> public:
>    //Constructors and stuff
>    const directory_t& directory() const {return directory_m;}
>
> };
.. Classes should of course hide their
> implementation but it seams very stupid not to have read access
> directly to a member. If I didn't have that I have to bloat the class
> with wrapper functions for the members.

I often wonder about this myself. Frankly this is one case where I
cant see the value added.

Kai-Uwe Bux

12/5/2008 3:14:00 AM

0

ma740988 wrote:

> On Dec 4, 5:15 am, Per <p...@isy.liu.se> wrote:
>
>> public:
>> //Constructors and stuff
>> const directory_t& directory() const {return directory_m;}
>>
>> };
> . Classes should of course hide their
>> implementation but it seams very stupid not to have read access
>> directly to a member. If I didn't have that I have to bloat the class
>> with wrapper functions for the members.
>
> I often wonder about this myself. Frankly this is one case where I
> cant see the value added.

It is somewhat analogous to using unnamed numerical constants (magic
numbers) vs. named numerical constants. So, as

struct X {

static unsigned long const number_of_cycles = 13;

};

is better than using 13 throughout the code, using X::directory_t instead of
std::map<std::string, int> is better for the same reasons. Think of using
std::map<std::string, int> as "magic typing".

Now, the documentation may even describe directory_t as implementation
defined and conforming to a certain interface of which map<string,int> is a
model. In that case, using map<string,int> as an implementation of
directory_t is not wrong; and client code that relies on this particular
implementation is broken (in the same way as vector<T>::iterator could be
implemented as T* and client code relying on this implementation is
broken). However, it is true that from a quality of implementation point of
view it would probably be better to do something like:

struct directory_t : private map<string,int> {
// some using ... to forward the parts of the interface
// required by directory_t
//
// some constructors
};


Best

Kai-Uwe Bux

Jeff Schwab

12/5/2008 3:17:00 AM

0

Kai-Uwe Bux wrote:
> ma740988 wrote:
>
>> On Dec 4, 5:15 am, Per <p...@isy.liu.se> wrote:
>>
>>> public:
>>> //Constructors and stuff
>>> const directory_t& directory() const {return directory_m;}
>>>
>>> };
>> . Classes should of course hide their
>>> implementation but it seams very stupid not to have read access
>>> directly to a member. If I didn't have that I have to bloat the class
>>> with wrapper functions for the members.
>> I often wonder about this myself. Frankly this is one case where I
>> cant see the value added.
>
> It is somewhat analogous to using unnamed numerical constants (magic
> numbers) vs. named numerical constants. So, as
>
> struct X {
>
> static unsigned long const number_of_cycles = 13;
>
> };
>
> is better than using 13 throughout the code, using X::directory_t instead of
> std::map<std::string, int> is better for the same reasons. Think of using
> std::map<std::string, int> as "magic typing".

That's a great analogy.