1

Consider a class like

class Element{
    public:
    // approx. size of ~ 2000 Byte
    BigStruct aLargeMember;

    // some method, which does not depend on aLargeMember
    void someMethod();
}

Now assume that many instances (e.g., 100,000,000 instances during runtime, with approx 50,000 instances existing at the same time) of Element are created during runtime, and very often only someMethod() is called, without any necessity of allocating memory for aLargeMember. (This illustrative example is derived from a nonlinear finite element code, class Element actually represents a finite element.)

Now my question: Since aLargeMember is not required very often, and considering the large number of instances of Element, would it be advantageous to create aLargeMember dynamically? For instance

class Element{
    public:
    // approx. size of ~ 2000 Byte
    std::unique_ptr<BigStruct> aLargeMember;

    // only called when aLargeMember is needed
    void initializeALargeMember{
        aLargeMember = std::unique_ptr<BigStruct>( new BigStruct() );}

    // some method, which does not depend on aLargeMember
    void someMethod();
}

Basically, this corresponds recommendation 4 given in https://stackoverflow.com/a/36646563/4859499:

Only use new if there's a clear need, such as:

  • an especially large allocation that would eat up much of the stack (your OS/process will have "negotiated" a limit, usually in the 1-8+ megabyte range)

    • if this is the only reason you're using dynamic allocation, and you do want the lifetime of the object tied to a scope in your function, you should use a local std::unique_ptr<> to manage the dynamic memory, and ensure it is released no matter how you leave the scope: by return, throw, break etc.. (You may also use a std::unique_ptr<> data member in a class/struct to manage any memory the object owns.)

So, my question is: Is the heap approach in the present case considered as bad practice? Or are there any good arguments against the heap in the present case? Thank you in advance!

trincot
  • 317,000
  • 35
  • 244
  • 286
mneuner
  • 433
  • 5
  • 25
  • Are you asking about [lazy initialization](https://en.wikipedia.org/wiki/Lazy_initialization)? – François Andrieux Sep 24 '18 at 19:22
  • 3
    Try both, profile, and see what the difference is. – NathanOliver Sep 24 '18 at 19:22
  • 1
    I think you have a good case for dynamic allocation here. One tweak: instead of `std::unique_ptr( new BigStruct() )` , use `std::make_unique()` if it's available. [Docs on `make_unique`](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) – user4581301 Sep 24 '18 at 19:25
  • You could use a pool to allocate a bunch of `BigStruct` s upfront and increase the pool size by some multiplier whenever it's limit is hit. – George Sep 24 '18 at 19:25
  • It may actually be advantageous to just allocate all your `Element` s upfront, then you can iterate them in a cache friendly way, and not incur a cache miss whenever `BigStruct` is accessed. – George Sep 24 '18 at 19:29
  • dynamic allocation. I wouldn't be surprised if you run out of the stack in some systems – Yucel_K Sep 24 '18 at 19:30
  • This is something you have to test. Create a benchmark. Write simplified versions of both that will accesses `aLargeMember` an appropriate number of times ( is great for generating good distributions). Time and see which is the best. Then implement it that way first. – Martin York Sep 24 '18 at 21:46
  • Also please stop using the terms heap Vs stack. These are not relevant in C++ (they are Java terms). You want automatic Vs dynamic storage. – Martin York Sep 24 '18 at 21:48

1 Answers1

5

C++ Class Member: Stack vs. Heap Allocation

Don't be confusing stack based allocation with having member variables of heap allocated objects. If you have a heap allocated object of this class:

class Element{
    public:
    // approx. size of ~ 2000 Byte
    BigStruct aLargeMember;

    // some method, which does not depend on aLargeMember
    void someMethod();
}

then nothing here is allocated on the stack.

Since aLargeMember is not required very often, and considering the large number of instances of Element, would it be advantageous to create aLargeMember dynamically?

The scenario you are describing is indeed a legitimate candidate for dynamic memory allocation, as having not doing so just makes you allocate way more than you need, for no gain -- so you don't have much other choice.

Is the heap approach in the present case considered as bad practice?

This question is a bit too general, and could too easily be interpreted as being opinion based, but in the context of the given snippet it seems you are referring to a scenario of allocating a member variable on a need-to-use-basis, and so going a la lazy initialization could be preferable in order to mitigate the overhead of having to manually maintain the initialization for each point in the code requiring so. For this, you can wrap access to this member to make sure you return something that is initialized. Simply for illustration, very not thread-safe, the idea is along these lines:

class Element{
private:
    // approx. size of ~ 2000 Byte
    std::unique_ptr<BigStruct> aLargeMember;

    // A wrapper through which you access aLargeMember
    BigStruct& GetLargeMember()
    {
        if (!aLargeMember)
        {
            aLargeMember = std::make_unique<BigStruct>();
        }

        return *aLargeMember;
    }

public:
    // some method, which might depend on aLargeMember
    void someMethod();
};

If you need passing it around to be used outside the class scope, then you'll be in danger of a dangling reference, as the allocated instance owning the unique_ptr might already be destroyed. If this is the case and you really want to guarantee against that, then unique_ptr isn't fitting, as you can only move it. Instead, consider using a shared_ptr and return the actual smart pointer from GetLargeMember().

Or are there any good arguments against the heap in the present case?

Not against using the heap here, but there are at least several patterns you can employ for your disposal. For example, given you intend this to have such large amounts of instances being created, but far less existing simultaneously, I'd seriously consider pooling the instances of class Element.

Geezer
  • 5,600
  • 18
  • 31