Would this be a valid use of inheritance?

J

Juha Nieminen

Suppose you want to make a smart pointer which is allocator-conscious,
so that it can be told which allocator was used to allocate the object,
so that the smart pointer will know to use the same allocator to
deallocate it. The traditional "kosher" way of doing that would be
something along the lines of:

//---------------------------------------------------------------------
template<typename Data_t, typename Alloc_t = std::allocator<Data_t> >
class SmartPtr
{
Data_t* data;
Alloc_t alloc;
// And whatever is needed for reference counting, if anything

void decRefCount()
{
// Decrement the reference count, and then:
if(<refcount is 0>)
{
alloc.destroy(data);
alloc.deallocate(data, 1);
}
}

public:
SmartPtr(Data_t* d, const Alloc_t& a):
data(d), alloc(a)
{
// init refcount, or whatever
}

// Plus the other necessary functions.
};
//---------------------------------------------------------------------

The problem with this is that even if the specified allocator is an
empty class, it will take 4 bytes (I assume 8 bytes in a 64-bit system)
of space as a member variable. At least gcc seems unable to optimize
that space away, even though it's not used for anything. This needlessly
increases the size of the smart pointer, even though the allocator
doesn't need any space.

So I was thinking, would this be a valid way of circumventing the problem?

//---------------------------------------------------------------------
template<typename Data_t, typename Alloc_t = std::allocator<Data_t> >
class SmartPtr: private Alloc_t
{
Data_t* data;
// And whatever is needed for reference counting, if anything

void decRefCount()
{
// Decrement the reference count, and then:
if(<refcount is 0>)
{
destroy(data);
deallocate(data, 1);
}
}

public:
SmartPtr(Data_t* d, const Alloc_t& a):
Alloc_t(a), data(d)
{
// init refcount, or whatever
}

// Plus the other necessary functions.
};
//---------------------------------------------------------------------

I know this is badly misusing inheritance for an ugly hack, but now an
empty allocator class will not increase the size of the smart pointer,
but the smart pointer can still be told which allocator to use to
destroy the object.

Can you think of any reason why this would be a very bad idea?
 
P

peter koch

  Suppose you want to make a smart pointer which is allocator-conscious,
so that it can be told which allocator was used to allocate the object,
so that the smart pointer will know to use the same allocator to
deallocate it. The traditional "kosher" way of doing that would be
something along the lines of:

//---------------------------------------------------------------------
template<typename Data_t, typename Alloc_t = std::allocator<Data_t> >
class SmartPtr
{
    Data_t* data;
    Alloc_t alloc;
    // And whatever is needed for reference counting, if anything

    void decRefCount()
    {
        // Decrement the reference count, and then:
        if(<refcount is 0>)
        {
            alloc.destroy(data);
            alloc.deallocate(data, 1);
        }
    }

 public:
    SmartPtr(Data_t* d, const Alloc_t& a):
        data(d), alloc(a)
    {
        // init refcount, or whatever
    }

    // Plus the other necessary functions.};

//---------------------------------------------------------------------

  The problem with this is that even if the specified allocator is an
empty class, it will take 4 bytes (I assume 8 bytes in a 64-bit system)
of space as a member variable. At least gcc seems unable to optimize
that space away, even though it's not used for anything. This needlessly
increases the size of the smart pointer, even though the allocator
doesn't need any space.

  So I was thinking, would this be a valid way of circumventing the problem?
[snip examply using private inheritance in order to take advantage of
empty base optimisation]
  I know this is badly misusing inheritance for an ugly hack, but now an
empty allocator class will not increase the size of the smart pointer,
but the smart pointer can still be told which allocator to use to
destroy the object.

  Can you think of any reason why this would be a very bad idea?

No - and I am not sure I really would call it a hack. For maintenance
purposes, I would document the purpose of the inheritance.

But are you quite sure that this is a good idea? What would happen if
you determine e.g. to allow assignment of one smart pointer to another
and the allocators differ?

/Peter
 
J

Juha Nieminen

peter said:
But are you quite sure that this is a good idea? What would happen if
you determine e.g. to allow assignment of one smart pointer to another
and the allocators differ?

I suppose I could simply take the same stance as the new C++ standard
with regard to list splicing between lists with differing allocators:
Undefined behavior.
 
G

gpderetta

Suppose you want to make a smart pointer which is allocator-conscious,
so that it can be told which allocator was used to allocate the object,
so that the smart pointer will know to use the same allocator to
deallocate it. The traditional "kosher" way of doing that would be
something along the lines of:
The problem with this is that even if the specified allocator is an
empty class, it will take 4 bytes (I assume 8 bytes in a 64-bit system)
of space as a member variable. At least gcc seems unable to optimize
that space away, even though it's not used for anything.

In fact it is not allowed to.

The standard requires every object of the same type must have a
distinct address, this means
that every object must have size at least 1. Otherwise, if foo had
size 0 in the following example:

struct foo {};

struct bar {
foo a, b;
} x;

x.a would have the same address of x.b.
This needlessly
increases the size of the smart pointer, even though the allocator
doesn't need any space.

So I was thinking, would this be a valid way of circumventing the problem?

I know this is badly misusing inheritance for an ugly hack, but now an
empty allocator class will not increase the size of the smart pointer,
but the smart pointer can still be told which allocator to use to
destroy the object.

Can you think of any reason why this would be a very bad idea?

No, in fact is a well known idiom that relies on an explicit exception
to the previous rule given by the standard: a compiler may overlap an
empty base with the derived class.
This is usually called Empty Base Optimization, and is implemented by
most compilers.
 
J

Juha Nieminen

While working on this, I ended up with this monstrosity of a line:

typename Allocator::template
rebind<size_t>::eek:ther(*this).deallocate(refCount, 1);

I'm wondering: How does the compiler decide what does the 'typename'
apply to?
 
J

Joe Greer

So I was thinking, would this be a valid way of circumventing the
problem?

//---------------------------------------------------------------------
template<typename Data_t, typename Alloc_t = std::allocator<Data_t> >
class SmartPtr: private Alloc_t
{
Data_t* data;
// And whatever is needed for reference counting, if anything

void decRefCount()
{
// Decrement the reference count, and then:
if(<refcount is 0>)
{
destroy(data);
deallocate(data, 1);
}
}

public:
SmartPtr(Data_t* d, const Alloc_t& a):
Alloc_t(a), data(d)
{
// init refcount, or whatever
}

// Plus the other necessary functions.
};
//---------------------------------------------------------------------

I know this is badly misusing inheritance for an ugly hack, but now
an
empty allocator class will not increase the size of the smart pointer,
but the smart pointer can still be told which allocator to use to
destroy the object.

Can you think of any reason why this would be a very bad idea?

I think that the same reason that smart_ptr<> is the way it is in the
standard is a good enough reason not to do this. Basically, since the
allocator is now part of the smart pointer type, it can't interact with
functions written to accept a smart pointer of type Data_t with a different
allocator. This can become a real pain in the backside for large projects.

joe
 
J

Juha Nieminen

Joe said:
Basically, since the
allocator is now part of the smart pointer type, it can't interact with
functions written to accept a smart pointer of type Data_t with a different
allocator.

No matter what you do, if a function expects a smart pointer pointing
to an object allocated with one allocator, and you give it an object
allocated with a different allocator, you have undefined behavior.

The issue is in no way different from STL containers supporting an
allocator: If something expects a std::list which uses the standard
allocator and you give it a std::list which uses a custom allocator, all
bets are off. I don't see how this is different from having smart
pointers support allocators in the same way.

Of course the best way is to implement that function which takes a
smart pointer to not to care what the allocator is, ie. template that
allocator (or better yet, the whole smart pointer) away.
 

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,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top