Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Composition using references

Reply
Thread Tools

Composition using references

 
 
davidrubin@warpmail.net
Guest
Posts: n/a
 
      12-11-2005
Alf P. Steinbach wrote:
> * Thomas Tutone:
> >
> > Bob Hairgrove wrote:
> > > On 10 Dec 2005 13:29:14 -0800, "Thomas Tutone"
> > > <> wrote:
> > >
> > > >Perhaps you are missing the point. ABC's destructor will never be
> > > >called directly - because the destructor is virtual, it will be the
> > > >destructor of the derived class that will be called, which will in turn
> > > >call the base destructor. The derived destructor has access to the
> > > >protected functions of its parent.
> > >
> > > This is not true. If the base class' destructor is protected, clients
> > > using the classes will not be able to call delete on a pointer to the
> > > base class. They will, however, be able to call delete on pointers to
> > > the derived classes.

> >
> > Yes - My answer was incorrect. Sorry about that.

>
> Well no. Your follow-up explanation was incorrect, but your original
> answer was just incomplete.
>
> Declaring the destructor protected in ABC is indeed a Good Thing To Do,
> because it means instances of the class _have_ to be dynamically
> allocated -- a standard compiler won't then allow anything else.
>
> Of course then the client code must be offered some means to deallocate.
> A bad way is to provide a delete-yourself public member function. A
> general and good way is to define a common destruction function
>
> template< typename T > callDelete( T* p ) { delete p; }
>
> and make that function (as well as std::auto_ptr) a friend of the class.


What about the case of custom allocators? Here the protocol for an
allocator provides a member template for deleting objects allocated
from a 'MyAllocator' (e.g., via placement-new). IME, this is a much
more useful idom than 'callDelete', it does not require friendship, and
it contradicts the "principle" that virtual destructors should be
protected.

class MyAllocator {
public:
virtual ~MyAllocator();

void *allocate(std::size_t size) = 0;
void deallocate(void *buffer) = 0;

template <typename TYPE>
void deleteObject(TYPE *object) {
object->~TYPE();
deallocate(object);
}
};

 
Reply With Quote
 
 
 
 
Alf P. Steinbach
Guest
Posts: n/a
 
      12-11-2005
* :
> Alf P. Steinbach wrote:
> > * Thomas Tutone:
> > >
> > > Bob Hairgrove wrote:
> > >
> > > Yes - My answer was incorrect. Sorry about that.

> >
> > Well no. Your follow-up explanation was incorrect, but your original
> > answer was just incomplete.
> >
> > Declaring the destructor protected in ABC is indeed a Good Thing To Do,
> > because it means instances of the class _have_ to be dynamically
> > allocated -- a standard compiler won't then allow anything else.
> >
> > Of course then the client code must be offered some means to deallocate.
> > A bad way is to provide a delete-yourself public member function. A
> > general and good way is to define a common destruction function
> >
> > template< typename T > callDelete( T* p ) { delete p; }
> >
> > and make that function (as well as std::auto_ptr) a friend of the class.

>
> What about the case of custom allocators? Here the protocol for an
> allocator provides a member template for deleting objects allocated
> from a 'MyAllocator' (e.g., via placement-new). IME, this is a much
> more useful idom than 'callDelete', it does not require friendship, and
> it contradicts the "principle" that virtual destructors should be
> protected.
>
> class MyAllocator {
> public:
> virtual ~MyAllocator();
>
> void *allocate(std::size_t size) = 0;
> void deallocate(void *buffer) = 0;
>
> template <typename TYPE>
> void deleteObject(TYPE *object) {
> object->~TYPE();
> deallocate(object);
> }
> };


You're not making sense to me.

First, there is no "principle" that's based on virtuality of destructor.
There is a technique to ensure dynamic allocation. Dynamic allocation +
inheritance generally implies virtual destructor, but not the other way
around.

Second, the existence of an an allocator class somewhere does not
prevent client code from declaring a static, local or member variable of
any type, and a placement allocation function doesn't, either.

Perhaps you meant to write something more, e.g. an example of a class
using that allocator, where somehow that class was restricted to dynamic
allocation (no such mechanism obvious in what you write), or perhaps
where that class encapsulated all dynamic allocation inside a
value-semantics interface, like the standard container classes (comment
about virtual destructor makes no sense then)?

Btw., allocators are mostly difficult to use properly, and I think in
the above the destructor should offer the no-throw guarantee, and the
allocate member should have typed result, centralizing the casting, like
std::allocator, and the deleteObject function should absolutely not be
templated (ever heard of type safety?); template the class instead.


Cheers,

- Alf

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
 
Reply With Quote
 
 
 
 
peter steiner
Guest
Posts: n/a
 
      12-11-2005

peter steiner wrote:
> Bob Hairgrove wrote:
> > On 10 Dec 2005 14:06:58 -0800, "peter steiner" <>
> > wrote:
> >
> > >> You shouldn't use names with a leading underscore for anything (see
> > >> 17.4.3.1.2 of the C++ standard).
> > >
> > >why not?
> > >
> > >one leading underscore followed by a lowercase letter is allowed for
> > >names anywhere but in global and std namespace. class members fall in
> > >neither category.

> >
> > Within class namespace, the global namespace is also visible ... it is
> > always visible. The whole point is to prevent users of the
> > implementation from declaring names which conflict with the names used
> > by the implementation, which can be in the global namespace. You have
> > no way of excluding those names.

>
> i stand corrected and enlightened. that should have been obvious, sorry
> for the hassle and confusion.


well, rereading the regarding section in the standard i think the
visibility of names is the reason why there are two different
specifications, one pattern reserved everywhere and one in global and
std namespace. the implementation has to use the first kind (__ and
_[A-Z]) for things that have to be visible below the global and std
namespace. seemingly there should be no issue with the other kind of
identifiers (_[a-z]) in user code.

 
Reply With Quote
 
Mateusz Loskot
Guest
Posts: n/a
 
      12-11-2005
Razzer wrote:
> Mateusz Loskot wrote:
> >
> > 17.4.3.1.2 Global names
> > 1. Certain sets of names and function signatures are always reserved
> > to the implementation:
> > - Each name that contains a double underscore _ _ or begins with
> > an underscore followed by an uppercase letter (2.11) is reserved to the
> > implementation for any use.
> > - Each name that begins with an underscore is reserved to the
> > implementation for use as a name in the global namespace.
> >
> >
> > Note, the only second part refers to global namespace.

>
> No. The meaning of the Standard is clear. You cannot use any identifier
> with a leading underscore as a name (in a declaration) when the thing
> declared is at the global (or std namespace) scope.


Yes, I'm convinced

Cheers
--
Mateusz Loskot
http://mateusz.loskot.net

 
Reply With Quote
 
davidrubin@warpmail.net
Guest
Posts: n/a
 
      12-12-2005

Alf P. Steinbach wrote:
> * :
> > Alf P. Steinbach wrote:
> > > * Thomas Tutone:
> > > >
> > > > Bob Hairgrove wrote:
> > > >
> > > > Yes - My answer was incorrect. Sorry about that.
> > >
> > > Well no. Your follow-up explanation was incorrect, but your original
> > > answer was just incomplete.
> > >
> > > Declaring the destructor protected in ABC is indeed a Good Thing To Do,
> > > because it means instances of the class _have_ to be dynamically
> > > allocated -- a standard compiler won't then allow anything else.
> > >
> > > Of course then the client code must be offered some means to deallocate.
> > > A bad way is to provide a delete-yourself public member function. A
> > > general and good way is to define a common destruction function
> > >
> > > template< typename T > callDelete( T* p ) { delete p; }
> > >
> > > and make that function (as well as std::auto_ptr) a friend of the class.

> >
> > What about the case of custom allocators? Here the protocol for an
> > allocator provides a member template for deleting objects allocated
> > from a 'MyAllocator' (e.g., via placement-new). IME, this is a much
> > more useful idom than 'callDelete', it does not require friendship, and
> > it contradicts the "principle" that virtual destructors should be
> > protected.
> >
> > class MyAllocator {
> > public:
> > virtual ~MyAllocator();
> >
> > void *allocate(std::size_t size) = 0;
> > void deallocate(void *buffer) = 0;
> >
> > template <typename TYPE>
> > void deleteObject(TYPE *object) {
> > object->~TYPE();
> > deallocate(object);
> > }
> > };

>
> You're not making sense to me.
>
> First, there is no "principle" that's based on virtuality of destructor.
> There is a technique to ensure dynamic allocation. Dynamic allocation +
> inheritance generally implies virtual destructor, but not the other way
> around.


I guess I should have said "suggestion," rather than "principle." The
suggestion that making such destructors protected also proliferates the
use of friends, which, I believe, is not considered a Good Thing to Do,
especially when those friendships are "long-distance" friendships
(i.e., in the Lakosian sense).

> Second, the existence of an an allocator class somewhere does not
> prevent client code from declaring a static, local or member variable of
> any type, and a placement allocation function doesn't, either.


True, but by the same token, I don't think one should encourage the
practice of making (base class) destructors protected and declaring
arbitrary friendships just because some deleter exists somewhere (e.g.,
std::auto_ptr). Also, the suggestion

> > > [...] to define a common destruction function
> > >
> > > template< typename T > callDelete( T* p ) { delete p; }
> > >
> > > and make that function (as well as std::auto_ptr) a friend of the class.


clearly does not scale.

> Perhaps you meant to write something more, e.g. an example of a class
> using that allocator, where somehow that class was restricted to dynamic
> allocation (no such mechanism obvious in what you write), or perhaps
> where that class encapsulated all dynamic allocation inside a
> value-semantics interface, like the standard container classes (comment
> about virtual destructor makes no sense then)?


Yes, I probably should have expounded a bit more on the use of the
allocator. What you mention above about encapsulating allocation is
exactly what I had in mind. I'm not sure what you mean by "comment
about virtual destructor makes no sense then," because 'MyAllocator'
instances must be able to delete object through base-class pointers.

> Btw., allocators are mostly difficult to use properly, and I think in
> the above the destructor should offer the no-throw guarantee, and the
> allocate member should have typed result, centralizing the casting, like
> std::allocator, and the deleteObject function should absolutely not be
> templated (ever heard of type safety?); template the class instead.


The 'allocate' method cannot have a typed result (other than 'char*')
because it is a general-purpose allocator (protocol). What type should
it return?

 
Reply With Quote
 
Alf P. Steinbach
Guest
Posts: n/a
 
      12-12-2005
* :
> Alf P. Steinbach wrote:
> >
> > First, there is no "principle" that's based on virtuality of destructor.
> > There is a technique to ensure dynamic allocation. Dynamic allocation +
> > inheritance generally implies virtual destructor, but not the other way
> > around.

>
> I guess I should have said "suggestion," rather than "principle." The
> suggestion that making such destructors protected also proliferates the
> use of friends, which, I believe, is not considered a Good Thing to Do,
> especially when those friendships are "long-distance" friendships
> (i.e., in the Lakosian sense).


It's a strictly limited set of friendships that can be implemented once and
for all by a macro.


> > Second, the existence of an an allocator class somewhere does not
> > prevent client code from declaring a static, local or member variable of
> > any type, and a placement allocation function doesn't, either.

>
> True, but by the same token, I don't think one should encourage the
> practice of making (base class) destructors protected and declaring
> arbitrary friendships just because some deleter exists somewhere (e.g.,
> std::auto_ptr).


Nothing is arbitrary. std::auto_ptr needs friendship because it's primitive.
boost::shared_ptr doesn't.


> Also, the suggestion
>
> > > > [...] to define a common destruction function
> > > >
> > > > template< typename T > callDelete( T* p ) { delete p; }
> > > >
> > > > and make that function (as well as std::auto_ptr) a friend of the class.

>
> clearly does not scale.


How so? How many different smartpointers that are as primitive as
std::auto_ptr, do you use in a project?


[snip]
> The 'allocate' method cannot have a typed result (other than 'char*')
> because it is a general-purpose allocator (protocol). What type should
> it return?


std::allocator is a general-purpose allocator.

std::allocator::allocate() returns T*.

Possibly something like uninitialized_storage<T> could be better than T*.

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
 
Reply With Quote
 
davidrubin@warpmail.net
Guest
Posts: n/a
 
      12-12-2005

Alf P. Steinbach wrote:
> * :
> > Alf P. Steinbach wrote:
> > >
> > > First, there is no "principle" that's based on virtuality of destructor.
> > > There is a technique to ensure dynamic allocation. Dynamic allocation +
> > > inheritance generally implies virtual destructor, but not the other way
> > > around.

> >
> > I guess I should have said "suggestion," rather than "principle." The
> > suggestion that making such destructors protected also proliferates the
> > use of friends, which, I believe, is not considered a Good Thing to Do,
> > especially when those friendships are "long-distance" friendships
> > (i.e., in the Lakosian sense).

>
> It's a strictly limited set of friendships that can be implemented once and
> for all by a macro.
>
>
> > > Second, the existence of an an allocator class somewhere does not
> > > prevent client code from declaring a static, local or member variable of
> > > any type, and a placement allocation function doesn't, either.

> >
> > True, but by the same token, I don't think one should encourage the
> > practice of making (base class) destructors protected and declaring
> > arbitrary friendships just because some deleter exists somewhere (e.g.,
> > std::auto_ptr).

>
> Nothing is arbitrary. std::auto_ptr needs friendship because it's primitive.
> boost::shared_ptr doesn't.


Why doesn't 'boost:shared_ptr' need friendship?

> > Also, the suggestion
> >
> > > > > [...] to define a common destruction function
> > > > >
> > > > > template< typename T > callDelete( T* p ) { delete p; }
> > > > >
> > > > > and make that function (as well as std::auto_ptr) a friend of the class.

> >
> > clearly does not scale.

>
> How so? How many different smartpointers that are as primitive as
> std::auto_ptr, do you use in a project?


I think the question is, how many different base classes do you have?
You need to change each one whenever you want to support a new deleter.
Moreover, you never know what your clients want to do. For example, how
do they use custom factories (such as 'MyAllocator')?

> [snip]
> > The 'allocate' method cannot have a typed result (other than 'char*')
> > because it is a general-purpose allocator (protocol). What type should
> > it return?

>
> std::allocator is a general-purpose allocator.
>
> std::allocator::allocate() returns T*.
>
> Possibly something like uninitialized_storage<T> could be better than T*.


The point of 'MyAllocator', although not obvious from my description,
is that it encapsulates allocation for an object, meaning that it
allocates both the object, and the memory use by the object. In fact,
you aptly pointed out this usage yourself. For example:

class Foo {
MyAllocator *d_allocator_p; // memory allocator (held)
char *d_buffer_p; // owned
Fee d_fee; // allocates memory

public:
Foo(MyAllocator *basicAllocator)
: d_allocator_p(basicAllocator)
, d_fee(basicAllocator)
{
d_buffer_p = (char*)d_allocator_p->allocate(INITIAL_SIZE);
}

~Foo()
{ d_allocator_p->deallocate(d_buffer_p); }

//...
};

 
Reply With Quote
 
Earl Purple
Guest
Posts: n/a
 
      12-12-2005

wrote:
> Why doesn't 'boost:shared_ptr' need friendship?


because you can pass in a deleter (the second parameter in its
constructor).

 
Reply With Quote
 
Earl Purple
Guest
Posts: n/a
 
      12-12-2005

wrote:
>
> You are trying too hard to mix the interface with implementation. There
> is *nothing* about the definition of 'ABC' that suggests derived types
> need an instance of 'AnotherABC'. Let 'ABC' be a pure abstract class
> (no data members), and let derived types use whatever members they need
> to implement the interface.


We don't know the exact circumstances here but it may well be that
there is a certain amount of the template pattern here.

ABC here obviously has a clear dependency on AnotherABC's interface, it
could well be that the difference is in the lifetime of the objects
(AnotherABC obviously has a longer lifetime) otherwise the classes are
closely coupled.

 
Reply With Quote
 
davidrubin@warpmail.net
Guest
Posts: n/a
 
      12-13-2005

Earl Purple wrote:
> wrote:
> >
> > You are trying too hard to mix the interface with implementation. There
> > is *nothing* about the definition of 'ABC' that suggests derived types
> > need an instance of 'AnotherABC'. Let 'ABC' be a pure abstract class
> > (no data members), and let derived types use whatever members they need
> > to implement the interface.

>
> We don't know the exact circumstances here but it may well be that
> there is a certain amount of the template pattern here.


This does feel like the template pattern, but seems very misplaced.

> ABC here obviously has a clear dependency on AnotherABC's interface, it
> could well be that the difference is in the lifetime of the objects
> (AnotherABC obviously has a longer lifetime) otherwise the classes are
> closely coupled.


The only thing you can tell is that 'ABC' is underspecified. It has an
reference to 'AnotherABC', and a virtual destructor. No other details
are given.

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
references and composition Nick Valeontis C++ 4 11-15-2009 03:40 PM
override when using object composition Tony Johansson C++ 1 09-03-2005 11:11 AM
Difference between bin and obj directories and difference between project references and dll references jakk ASP .Net 4 03-22-2005 09:23 PM
Object Composition & Returning References.... Patrick C++ 4 02-26-2004 02:56 PM
Pointers and References (and References to Pointers) Roger Leigh C++ 8 11-17-2003 10:14 AM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57