Composition using references

D

davidrubin

Alf said:
* Thomas Tutone:

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);
}
};
 
A

Alf P. Steinbach

* (e-mail address removed):
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
 
P

peter steiner

peter said:
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.
 
M

Mateusz Loskot

Razzer said:
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
 
D

davidrubin

Alf said:
* (e-mail address removed):

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?
 
A

Alf P. Steinbach

* (e-mail address removed):
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.

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*.
 
D

davidrubin

Alf said:
* (e-mail address removed):

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



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); }

//...
};
 
E

Earl Purple

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.
 
D

davidrubin

Earl said:
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.
 
A

Alf P. Steinbach

* (e-mail address removed):
Why doesn't 'boost:shared_ptr' need friendship?

Already answered by Earl Purple, but: because you can specify a deleter.

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.
Nope.


Moreover, you never know what your clients want to do. For example, how
do they use custom factories (such as 'MyAllocator')?

This is a technique for _restricting_ allocation to 'new'. Another such
technique is to make constructors inaccessible and use factory functions. No
matter which such technique is used, client code is restricted in the
allocation policies it can use, and that's the point.

With the restriction in place other policies can be supported, enabled,
globally by replacing ::eek:perator new, or per class e.g. by inheriting from a
class defining operator new and operator delete.

One can then arrange -- if needed -- that the client code specifies the
allocation policy via a policy template parameter, or via a static allocator
instance, or whatever. Generally allocation policies are per class, set of
classes or globally. I don't see any advantage to allocation policies
specified per-object by the client code.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top