8

My application uses a large amount of Panda objects. Each Panda has a list of Bamboo objects. This list does not change once the Panda is initialized (no Bamboo objects are added or removed). Currently, my class is implemented as follows:

class Panda
{
    int a;
    int b;
    int _bambooCount;
    Bamboo* _bamboo;

    Panda (int count, Bamboo* bamboo)
    {
        _bambooCount = count;
        _bamboo = new Bamboo[count];

        // ... copy bamboo into the array ...
    }
}

To alleviate the overhead of allocating an array of Bamboo objects, I could implement this class as follows -- basically, instead of creating objects via the regular constructor, a construction method allocates a single memory block to hold both the Panda object and its Bamboo array:

class Panda
{
    int a;
    int b;

    Panda ()
    {
        // ... other initializations here ...
    }

    static Panda *createPanda (int count, Bamboo* bamboo)
    {
        byte* p = new byte[sizeof(Panda) +
                           sizeof(Bamboo) * count];
        new (p) Panda ();

        Bamboo* bamboo = (Bamboo*)
            p + sizeof(Panda);

        // ... copy bamboo objects into the memory
        // behind the object...

        return (Panda*)p; 
    }
}

Can you foresee any problems with the second design, other than the increased maintenance effort? Is this an acceptable design pattern, or simply a premature optimization that could come back to bite me later?

Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
Tony the Pony
  • 40,327
  • 71
  • 187
  • 281
  • It will bite. And it will bite hard. – David Rodríguez - dribeas Sep 07 '09 at 17:03
  • 1
    You're breaking most of the semantics for class types in C++. Variable-length objects do not exist in C++. – jalf Sep 07 '09 at 17:30
  • 2
    If you're interested in eye-watering hacks of this nature, you might also like to take a look at the TBuf8 and TBuf16 class templates from Symbian/C++. – Steve Jessop Sep 07 '09 at 18:45
  • 1
    "...to alleviate the overhead of allocating an array of `Bamboo` objects..." First go and measure whether this is worth doing. When you have found hard numbers showing a need to, come back and ask what to do to improve a simple design to be faster. Maybe the above is a good solution to the (so far, not existing) problem. IMO it's most likely not. – sbi Sep 07 '09 at 21:18
  • Probably moot at this point, but the cast of `p` to `Bamboo*` occurs before the addition of `sizeof(Panda)`, so your `bamboo` will (eats) overshoots (and leaves), the `Panda` by a factor of the size of a pointer (i.e. 4 to 8 times). – pat Sep 08 '16 at 16:03

7 Answers7

11

C++ gives you another option. You should consider using std::vector.

class Panda
{
    int a;
    int b;
    std::vector<Bamboo> bamboo;
    // if you do not want to store by value:
    //std::vector< shared_ptr<Bamboo> > bamboo;

    Panda (int count, Bamboo* bamb) : bamboo( bamb, bamb+count ) {}
}

If you want to store Panda and Bamboos in continuous memory you could use solution from this article. The main idea is to overload operator new and operator delete.

Kirill V. Lyadvinsky
  • 97,037
  • 24
  • 136
  • 212
  • You must be aware of the side effects of this type of solutions and the problems you will have to deal with: users won't be able to use the objects inside containers (notably std::vector that preallocates memory based on the size of the type passed in) This trickery will endup with code that is not only hard to maintain (the questioner knows) but unnatural to use – David Rodríguez - dribeas Sep 07 '09 at 18:09
  • The most natural is to use `std::vector`. All other solutions has very limited use and should be applied with caution. – Kirill V. Lyadvinsky Sep 07 '09 at 18:42
  • If I'm not mistaken GCC's string implementation uses this kind of trickery. The trick is that the Panda object might contain a single pointer to the (overallocated) implementation class. – UncleBens Sep 07 '09 at 18:42
7

How do we convince people that in programming simplicity and clarity --in short: what mathematicians call 'elegance'-- are not a dispensable luxury, but a crucial matter that decides between success and failure?

-- Edsger W. Dijkstra

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
5

You'll be bitten if someone takes a Panda by value e.g.

//compiler allocates 16-bytes on the stack for this local variable
Panda panda = *createPanda(15, bamboo);

It may be acceptable (but is very probably a premature and horrible optimization) if you only ever refer to things by pointer and never by value, and if you beware the copy constructor and assignment operator.

ChrisW
  • 54,973
  • 13
  • 116
  • 224
3

Based on my experience, premature optimization is most always "premature".. That is to say you should profile your code and determine whether or not there is a need for optimization or you are just creating more work for yourself in the long run.

Also, it seems to me that the questions as to whether the optimization is worth it or not depends a lot on the size of the Bamboo class and the average number of Bamboo objects per Panda.

Mike Dinescu
  • 54,171
  • 16
  • 118
  • 151
3

This was find in C.
But in C++ there is no real need.

The real question is why do you want to do this?

This is a premature optimization, just use a std::vector<> internally and all your problems will disappear.

Because you are using a RAW pointer internally that the class owns you would need to override the default versions of:

  • Default Constructor
  • Destructor
  • Copy Constructor
  • Assignment operator
Martin York
  • 257,169
  • 86
  • 333
  • 562
3

If you're that desperate, you can probably do something like this:

template<std::size_t N>
class Panda_with_bamboo : public Panda_without_bamboo
{
    int a;
    int b;
    Bamboo bamboo[N];
}

But I believe you're not desperate, but optimizing prematurely.

sbi
  • 219,715
  • 46
  • 258
  • 445
1

You use "new" look of new operator. It is fully correct relative Panda, but why don't you use Bamboo initializer?

Dewfy
  • 23,277
  • 13
  • 73
  • 121