For a data member, is there a difference between the dynamic distribution of this variable (or not) if the contained object is already in dynamic memory?

I start with the assumption that, as a rule, it is recommended to allocate small objects on the stack and large objects in dynamic memory. Another assumption is that I may have gotten confused trying to find out about memory, STL containers, and smart pointers.

Consider the following example, where I have an object that is necessarily distributed in free storage using a smart pointer, and I can rely on clients receiving the said object from the factory, for example. This object contains some data that is specifically allocated using the STL container, which is std :: vector. In one case, this data vector itself is dynamically allocated using some smart pointer, and in another situation, I simply do not 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 both design options to have the same result, but right? Is there an advantage or disadvantage in choosing A or B? In particular, should I even choose design A because it is simpler or there are more considerations? Is B morally wrong because it can hang out for std :: vector?

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

edit: Related answers pointed to useful additional information for someone I was confused about. Using objects or pointers to objects as members of a class and allocating memory and Members of a class that are objects - pointers or not? C ++ And changing some google keywords will lead me to. When are vectors allocated, do they use memory on the heap or stack?

+6
source share
3 answers

Using std::unique_ptr here is simply wasteful unless your target is a compiler firewall (basically hides the compilation time dependency with the vector, but then you need a forward declaration for standard containers).

You add indirection, but, more importantly, the full content of SomeClass turns into 3 separate memory blocks for loading when accessing the content ( SomeClass combined with / containing unique_ptr's block pointing to std::vector's block pointing to its array of elements). In addition, you pay one extra extra level of heap overhead.

Now you can come up with scenarios in which indirect handling is useful for vector , for example, perhaps you can finely move / swap unique_ptrs between two instances of SomeClass . Yes, but vector already provides that without a unique_ptr wrapper unique_ptr top. And it already has states like empty that you can reuse some validity/nilness .

Remember that variable-sized containers themselves are small objects, not large objects that point to potentially large blocks. vector is small, its dynamic content may be. The idea of โ€‹โ€‹adding pointers to large objects is not a bad rule of thumb, but vector not a large object. With the semantics of moving in place, you should think of it more as a small block of memory, indicating a large one that can be finely copied and swapped cheaply. Before moving on to semantics, there was more reason to think of something like std::vector as an irreplaceably large object (although its contents were always replaced), but now you should think of it rather as a small descriptor pointing to a large dynamic content.

Some common reasons for introducing indirection through something like unique_ptr :

  • Abstraction and hiding. If you are trying to abstract or hide the specific definition of a type / subtype, Foo , then you need indirectness so that its handle can be captured (or perhaps even used with abstraction) by those who know exactly what Foo .
  • To allow a large, continuous object with 1 block type to be passed from owner to owner without invoking a copy or nullifying references / pointers (including iterators) to it or its contents.
  • It seems that it is wasteful, but sometimes useful in an urgent endeavor - to simply enter a validity/null state that essentially does not have it.
  • It is sometimes useful to use optimizations to pull out some of the less frequently visited larger members of an object so that its regular access elements are more neat (and possibly with neighboring objects) in the cache line. There, unique_ptr can allow you to share this memory layout of the object, still corresponding to RAII.

Now packing shared_ptr on top of a standard container may have more legitimate applications if you have a container that can actually be the owner (reasonably) of more than one owner. With unique_ptr only one owner can own an object at a time, and standard containers already allow you to change and move each other with their internal guts (large dynamic parts). Therefore, there are very few reasons why I can think of wrapping a standard container directly with unique_ptr , since it already looks a bit like a smart pointer to a dynamic array (but with more functionality for working with these dynamic data, including deep copy, if optional).

And if we are talking about non-standard containers, for example, we say that you are working with a third-party library that provides some data structures whose contents can become very large, but they cannot provide these cheap, invalid move/swap semantics, then you can superficially wrap it around unique_ptr , exchanging some of its creation / access / destruction rights to return these cheap move/swap semantics as a workaround. For standard containers, this workaround is not required.

+3
source

std::unique_ptr<std::vector<double>> is slower, takes up more memory, and the only advantage is that it contains an additional possible state: "vector does not exist." However, if you care about this condition, use boost::optional<std::vector> . You will almost never have a container allocated by the heap, and certainly never use unique_ptr . It actually works great, doesn't โ€œhang out,โ€ it's just pointlessly slow.

+8
source

I agree with @MooingDuck ; I do not think that using std::unique_ptr has any convincing advantages. However, I could use a use case for std::shared_ptr if the element data is very large and the class will support COW semantics (copy-on-write) (or any other use case when the data is shared between multiple instances).

+2
source

Source: https://habr.com/ru/post/986698/


All Articles