6

I'm starting with the assumption that, generally, it is a good idea to allocate small objects in the stack, and big objects in dynamic memory. Another assumption is that I'm possibly confused while trying to learn about memory, STL containers and smart pointers.

Consider the following example, where I have an object that is necessarily allocated in the free store through a smart pointer, and I can rely on clients getting said object from a factory, for instance. This object contains some data that is specifically allocated using an STL container, which happens to be a std::vector. In one case, this data vector itself is dynamically allocated using some smart pointer, and in the other situation I just don't use a smart pointer.

Is there any practical difference between design A and design B, described below?

Situation A:

class SomeClass{
public:
    SomeClass(){ /* initialize some potentially big STL container */ }
private:
    std::vector<double> dataVector_;
};

Situation B:

class SomeOtherClass{
public:
    SomeOtherClass() { /* initialize some potentially big STL container,
                        but is it allocated in any different way? */ }
private:
    std::unique_ptr<std::vector<double>> pDataVector_;
};

Some factory functions.

std::unique_ptr<SomeClass> someClassFactory(){
    return std::make_unique<SomeClass>();
}

std::unique_ptr<SomeOtherClass> someOtherClassFactory(){
    return std::make_unique<SomeOtherClass>();
}

Use case:

int main(){
    //in my case I can reliably assume that objects themselves
    //are going to always be allocated in dynamic memory
    auto pSomeClassObject(someClassFactory());
    auto pSomeOtherClassObject(someOtherClassFactory());

    return 0;
}

I would expect that both design choices have the same outcome, but do they? Is there any advantage or disadvantage for choosing A or B? Specifically, should I generally choose design A because it's simpler or are there more considerations? Is B morally wrong because it can dangle for a std::vector?

tl;dr : Is it wrong to have a smart pointer pointing to a STL container?

edit: The related answers pointed to useful additional information for someone as confused as myself. Usage of objects or pointers to objects as class members and memory allocation and Class members that are objects - Pointers or not? C++ And changing some google keywords lead me to When vectors are allocated, do they use memory on the heap or the stack?

Community
  • 1
  • 1
arthropod
  • 124
  • 1
  • 10

3 Answers3

8

std::unique_ptr<std::vector<double>> is slower, takes more memory, and the only advantage is that it contains an additional possible state: "vector doesn't exist". However, if you care about that state, use boost::optional<std::vector> instead. You should almost never have a heap-allocated container, and definitely never use a unique_ptr. It actually works fine, no "dangling", it's just pointlessly slow.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 1
    `end` iterators of `unique_ptr>` are guaranteed to be valid after move and swap of the `unique_ptr`. – dyp May 07 '15 at 00:32
3

Using std::unique_ptr here is just wasteful unless your goal is a compiler firewall (basically hiding the compile-time dependency to vector, but then you'd need a forward declaration to standard containers).

You're adding an indirection but, more importantly, the full contents of SomeClass turns into 3 separate memory blocks to load when accessing the contents (SomeClass merged with/containing unique_ptr's block pointing to std::vector's block pointing to its element array). In addition you're paying one extra superfluous level of heap overhead.

Now you might start imagining scenarios where an indirection is helpful to the vector, like maybe you can shallow move/swap the unique_ptrs between two SomeClass instances. Yes, but vector already provides that without a unique_ptr wrapper on top. And it already has states like empty that you can reuse for some concept of validity/nilness.

Remember that variable-sized containers themselves are small objects, not big ones, pointing to potentially big blocks. vector isn't big, its dynamic contents can be. The idea of adding indirections for big objects isn't a bad rule of thumb, but vector is not a big object. With move semantics in place, it's worth thinking of it more like a little memory block pointing to a big one that can be shallow copied and swapped cheaply. Before move semantics, there were more reasons to think of something like std::vector as one indivisibly large object (though its contents were always swappable), but now it's worth thinking of it more like a little handle pointing to big, dynamic contents.

Some common reasons to introduce an indirection through something like unique_ptr is:

  1. Abstraction & hiding. If you're trying to abstract or hide the concrete definition of some type/subtype, Foo, then this is where you need the indirection so that its handle can be captured (or potentially even used with abstraction) by those who don't know exactly what Foo is.
  2. To allow a big, contiguous 1-block-type object to be passed around from owner to owner without invoking a copy or invalidating references/pointers (iterators included) to it or its contents.
  3. A hasty kind of reason that's wasteful but sometimes useful in a deadline rush is to simply introduce a validity/null state to something that doesn't inherently have it.
  4. Occasionally it's useful as an optimization to hoist out certain less frequently-accessed, larger members of an object so that its commonly-accessed elements fit more snugly (and perhaps with adjacent objects) in a cache line. There unique_ptr can let you split apart that object's memory layout while still conforming to RAII.

Now wrapping a shared_ptr on top of a standard container might have more legitimate applications if you have a container that can actually be owned (sensibly) by more than one owner. With unique_ptr, only one owner can possess the object at a time, and standard containers already let you swap and move each other's internal guts (the big, dynamic parts). So there's very little reason I can think of to wrap a standard container directly with a unique_ptr, as it's already somewhat like a smart pointer to a dynamic array (but with more functionality to work with that dynamic data, including deep copying it if desired).

And if we talk about non-standard containers, like say you're working with a third party library that provides some data structures whose contents can get very large but they fail to provide those cheap, non-invalidating move/swap semantics, then you might superficially wrap it around a unique_ptr, exchanging some creation/access/destruction overhead to get those cheap move/swap semantics back as a workaround. For the standard containers, no such workaround is needed.

  • 1
    Changed the accepted answer to this one due to its completeness and because it cleared misconceptions that led me to make this question. – arthropod May 07 '15 at 12:22
2

I agree with @MooingDuck; I don't think using std::unique_ptr has any compelling advantages. However, I could see a use case for std::shared_ptr if the member data is very large and the class is going to support COW (copy-on-write) semantics (or any other use case where the data is shared across multiple instances).

Community
  • 1
  • 1
James Adkison
  • 9,412
  • 2
  • 29
  • 43