boost pool per class...

W

werasm

Hi,

This is not boost related per sé, therefore I'm posing the question
here.

I need to do many small object allocations, and I'm considering
using boost::pool for this. I've contemplated using Coplien's CRTP
to encapsulate the allocation/de-allocation and the pool per type.

Does anybody see a potential caveat here? One potential caveat
would be if the derived class overloads new/delete himself, but
that defeats the point of deriving in the first place. I made
the destructor protected as deletion would be through the
interface of the derived type - so to speak.

Comments welcome.

Werner

Example here:

#include <memory>

//Just to get things to compile...
class Default{};

template <class Alloc = Default>
struct BoostPool
{ explicit BoostPool( unsigned size); };

//Encapsulates small object allocation
// per type.
template <class AllocT>
class BoostPoolAllocatable
{
public:
static void* operator new( size_t );
static void operator delete( void*, size_t );

protected:
virtual ~BoostPoolAllocatable(){ }

private:
static BoostPool<> myPool_;
};
template <class AllocT>
BoostPool<> BoostPoolAllocatable<AllocT>::myPool_( sizeof(AllocT) );

//The small object...
struct SmallObject : BoostPoolAllocatable<SmallObject>
{
};

//Test
int main()
{
std::auto_ptr<SmallObject> x( new SmallObject );
}
 
W

werasm

//Just to get things to compile...
class Default{};

template <class Alloc = Default>
struct BoostPool
{ explicit BoostPool( unsigned size); };

//Encapsulates small object allocation
// per type.
template <class AllocT>
class BoostPoolAllocatable
{
public:
static void* operator new( size_t );
static void operator delete( void*, size_t );

protected:
virtual ~BoostPoolAllocatable(){ }

private:
static BoostPool<> myPool_;};

template <class AllocT>
BoostPool<> BoostPoolAllocatable<AllocT>::myPool_( sizeof(AllocT) );

//The small object...
struct SmallObject : BoostPoolAllocatable<SmallObject>
{

};

Excuse me for making a habit of replying to my own posts :-|. I
should probably add operator new[] and delete[] operators as
well. Consider it done. Any other comments welcome.
 
M

Marco Manfredini

^^^^^^^^^^^^
static initialization order can be hazardous. boost pool has explicitly
for this reason and your purpose 'singleton_pool' defined.
 
W

werasm

^^^^^^^^^^^^
static initialization order can be hazardous. boost pool has explicitly
for this reason and your purpose 'singleton_pool' defined.

Given the fact that a base's translation unit must be visible in
translation units of classes that derives from it, I do not deem
this a problem. If you consider 3.6.2:1 states that order of
initialization in the same translation unit depends on order
of definition. Clearly the definition of the static member
exists before those of the derived classes, not? Your
comments appreciated (quoting the standard in this case).

Kind regards,

Werner
 
M

Marco Manfredini

werasm said:
Given the fact that a base's translation unit must be visible in
translation units of classes that derives from it, I do not deem
this a problem. If you consider 3.6.2:1 states that order of
initialization in the same translation unit depends on order
of definition. Clearly the definition of the static member
exists before those of the derived classes, not? Your
comments appreciated (quoting the standard in this case).

Kind regards,

Werner

The initialization order withing the TU is not the problem, but the fact
that two or more TU's may use each other during SI. The initialization
order is not specified in this case. Most compilers try to solve this by
performing a topological sort on the cross module dependencies during
the linker phase, but this isn't foolproof and not always possible in
advance (shared libraries). Some compilers even have a pragma to specify
a priority manually.

Marco
 
W

werasm

The initialization order withing the TU is not the problem, but the fact
that two or more TU's may use each other during SI.

I value your contribution, but I still don't see how it can be a
problem
in this case. The entire boost::pool library is implemented in header
files (no shared library here). This implies that all definitions must
exist prior to usage in any translation unit (TU). In this case I
cannot
see how two ore more TU's use each other, as only one is relevant -
that
of the derived class. Perhaps you could strengthen your argument with
an example where this case would fail. I can think of cases where I
know Static initialisation (SI) would be a problem, but not in this
case.
Someone, correct me if I'm wrong (In this case SI cannot be a problem
as interdependent TUs are impossible).
The initialization order is not specified in this case.

In what case, the example mentioned, or in cases where TUs are
really interdependent?
Most compilers try to solve this by
performing a topological sort on the cross module dependencies during
the linker phase, but this isn't foolproof and not always possible in
advance (shared libraries).

Which certainly is not applicable here, but point taken and good to
know.

Thanks, Regards,

Werner
 
M

Marco Manfredini

werasm said:
In what case, the example mentioned, or in cases where TUs are
really interdependent?

For example when I use an object without knowing it's complete definition.

a.cc:
class A : public SomeInterface, pooled<A> {};
SomeInterface *makeA() { return new A(); }

b.cc:
extern SomeInterface *makeA();
struct Init{
Init() { makeA(); }
} _init;

Now your fate is in the hand of the linker's ability you see that an
partial init-ordering a<b exists. My examples isn't even artificially
constructed, because this is a typical scenario when you are working
with an Interface/Factory pattern where the implementations are hidden
in separate TU's.

Marco
 
W

werasm

For example when I use an object without knowing it's complete definition.

a.cc:
class A : public SomeInterface, pooled<A> {};
SomeInterface *makeA() { return new A(); }

b.cc:
extern SomeInterface *makeA();
struct Init{
Init() { makeA(); }

} _init;

Now your fate is in the hand of the linker's ability you see that an
partial init-ordering a<b exists. My examples isn't even artificially
constructed, because this is a typical scenario when you are working
with an Interface/Factory pattern where the implementations are hidden
in separate TU's.

Thank you. Clearer now. Appreciate your time and example and I
have to agree then.

Regards,

Werner
 
J

James Kanze

I think you're confusing declarations and definitions (which
admittedly is a bit confusing here), and what is one and what is
the other. The definition of the base class must be available
in order to define the derived class. But the declaration of a
static data member within the class definition is not a
definition. The member must be defined elsewhere, in a single
translation unit. If you try to use it in the static
initialization in some other translation unit, you have
undefined behavior. (In general---there are a few tricky idioms
which avoid this. My FixedLengthAllocator, for example, has an
option which allows using it before it is constructed, provided
the instance has static lifetime, and the compiler doesn't go
overboard with error checking---to date, none I've encountered
do.)
The initialization order withing the TU is not the problem,
but the fact that two or more TU's may use each other during
SI. The initialization order is not specified in this case.
Most compilers try to solve this by performing a topological
sort on the cross module dependencies during the linker phase,
but this isn't foolproof and not always possible in advance
(shared libraries). Some compilers even have a pragma to
specify a priority manually.

Most compilers do NOT try to solve the problem. Most compilers
just do the TU's in some arbitrary order: typicallly either in
the order the TU's were incorporated into the program (which
isn't always controlable where libraries are involved) or the
inverse order (which means that people tend not to get into
trouble if they've failed to take the necessary precautions when
using std::cout et al in static constructors, since the C++
library is typically incorporated at the end).

If the compiler does do such analysis, the "correct" behavior
(from a QoI point of view---anything it does is correct as far
as the standard is concerned) would be to generate an error
any time if found a dependency.
 
J

James Kanze

I value your contribution, but I still don't see how it can be
a problem in this case. The entire boost::pool library is
implemented in header files (no shared library here). This
implies that all definitions must exist prior to usage in any
translation unit (TU).

All definitions of the templates, yes. We're not talking about
the definition of a template here, however; we're talking about
the definition of an object. Class templates are not objects.
Static data members of class templates are not objects either.
The instantiation of a static data member of a class template is
an object (and that's the issue here). The fact that it's a
template makes the order of initialization problem worse, since
the standard explicitly says that the order is undefined, even
within a single TU (doubtlessly to avoid getting into all of the
problems which would arise if the order depended on the point of
instantiation and the which TU generated the instantiation which
was actually used).
In this case I cannot see how two ore more TU's use each
other, as only one is relevant - that of the derived class.
Perhaps you could strengthen your argument with an example
where this case would fail. I can think of cases where I know
Static initialisation (SI) would be a problem, but not in this
case. Someone, correct me if I'm wrong (In this case SI
cannot be a problem as interdependent TUs are impossible).

#include <cassert>
#include <iostream>
std::ios::Init dummyForInit ;

class S
{
public:
S( char const* p ) ;
char const* getP() const ;

private:
char const* myP ;
} ;

template< typename T >
struct C
{
static S ourS ;
} ;

template< typename T >
S C<T>:: ourS = "whatever" ;

class SS
{
public:
SS() ;
} ;

SS ss ;

int
main()
{
}

S::S(
char const* p )
: myP( p )
{
}

char const*
S::getP() const
{
assert( myP != NULL ) ;
return myP ;
}

SS::SS()
{
std::cout << C<int>::eek:urS.getP() << std::endl ;
}

With templates, you don't even need separate TU's. (This code
generates an assertion failure with g++ under Linux. And
probably with a lot of other compilers as well.)
 

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

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,130
Latest member
MitchellTe
Top