3

Let's say I have class A and I want it to have a deque and a vector. What are the advantages and disadvantages of using pointers for these collections?

class A 
{
  unique_ptr<deque<t>> a;
  deque<t>* b;
  deque<t> c;

  unique_ptr<vector<t>> x;
  vector<t>* y;
  vector<t> z;
}

I'm not sure I understand which to use when, as I rarely see pointers used for collections in classes.

  • 7
    *"I rarely see pointers used for collections"* - I suspect that's your answer right there. – StoryTeller - Unslander Monica Apr 30 '19 at 13:51
  • For the most part avoid using pointers. There will be cases where you need them but you can write a lot of code that doesn't use them at all. – NathanOliver Apr 30 '19 at 13:52
  • Except for corner cases (empty, very short maybe), both ``std::vector`` and ``std::dequeue`` use heap based backing storage in their implementation. Note that ``sizeof(std::vector)`` is constant, no matter how many elements it stores. Hence, use them directly without pointers as all you do is add another heap operation (and loss of locality) if you use pointers. – BitTickler Apr 30 '19 at 14:00
  • 2
    Adding an additional level of indirection introduces complexity. Ask yourself what you have to gain from doing this, and whether or not it's worth the cost. – François Andrieux Apr 30 '19 at 14:01
  • 1
    @BitTickler: "small string optimization" in most standard collections, particularly including `std::vector`, is illegal. You simply cannot meet the semantics of `std::swap` when even a single element is stored inline in the collection. – Ben Voigt Apr 30 '19 at 14:08
  • @BenVoigt I cannot see anything in both ``std::swap`` and ``Swappable`` on http://cppreference.com which supports your claim. You can even swap between different types. So how can the implementation of the container of an element be an issue? – BitTickler Apr 30 '19 at 15:41
  • @BitTickler: [`swap` on a vector is not allowed to move or swap any of the constituent elements. Not O(1) moves. None at all.](https://en.cppreference.com/w/cpp/container/vector/swap) The standard guarantees that **the address of elements inside the vector does not change** when you call `swap` on the vector. All the prior elements of vector A, without moving to a new location, are now in B. And all the prior elements of B are now in A. That is absolutely incompatible with storage embedded in the vector, indirection must be used. – Ben Voigt May 01 '19 at 00:45
  • By itself, that's enough to prevent small string optimization, but just to prove that it also follows from `std::swap` as I claimed, [`std::swap` is specialized for `std::vector` to call the vector `swap` member function](https://en.cppreference.com/w/cpp/container/vector/swap2) – Ben Voigt May 01 '19 at 00:49

3 Answers3

5

Use std::unique_ptr or std::shared_ptr if you need actual dynamic resources (shared if it needs to be a shared dynamic resource).

Use raw pointer or references if you need to refer to an object that you don't own (i.e. you're not going to destroy/deallocate it). Use pointer rather than reference if you ever need to "re-point" it to a different object.

Otherwise use by-value (i.e. non-dynamic) members/variables.

sanyassh
  • 8,100
  • 13
  • 36
  • 70
Cruz Jean
  • 2,761
  • 12
  • 16
  • the first part could mention that there is a difference between a `deque` containing shared dynamic objects or the `deque` itself being shared – 463035818_is_not_an_ai Apr 30 '19 at 14:01
  • I have never seen a case where a unique pointer to a standard container (except `std::array` which is crucially different from other containers) makes any sense. – eerorika Apr 30 '19 at 14:12
  • @eerorika Two potential ideas for reasons come to my mind: The standard container itself somehow resides in shared memory and some system with severe stack size limitations, assuming ``sizeof(container) > sizeof(uintptr_t)`` – BitTickler Apr 30 '19 at 21:29
  • @BitTickler I could *imagine* such cases too, but I've never seen in reality. Stack size limitations would have to be quite severe if ~2 pointers worth of memory is sufficient to justify the extra indirection and extra dynamic allocation (both total memory use and number of allocations). In such limited case I'd be surprised that dynamic memory is available in the first place. If I were in such situation though, I might consider implementing a custom `my::vector` with `sizeof(my::vector) == sizeof(uintptr_t)`. – eerorika Apr 30 '19 at 21:46
1

What are the advantages and disadvantages of using pointers for these collections?

A pointer can point to an object (such as a collection) stored elsewhere, outside of the object that contains the pointer.

Whether this is an advantage or a disadvantage compared to storing the object (such as a collection) within the class depends on the use case.

Should I Use Pointers to Collections in Classes?

It depends on the use case of the classes. If you need to point to something, then use a pointer. If you don't need to, then it is better to not use a pointer.

Storing objects (such as collections) in members makes the object lifetime considerations simple because the lifetime of the member is bound to the lifetime of the super object. Adding indirection (by the means of a pointer) introduces complexity since objects stored elsewhere don't necessarily share the lifetime of the pointer.

All of this applies to all objects in general, including but not limited to containers.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Dynamic allocations usually means that the object should have a programmatically controlled lifetime. That means that when using pointers and dynamic allocation, you are now in control of when the object is created and destroyed in the program instead of the usualy scoped lifetimes.

Adding unneeded levels of indirections can be harmful while not adding anything useful to your program. It may also have a maintenace cost and a performance cost and in some cases a security cost.

But this can be useful in certain cases, where you actually need that level of indirection. For example, some part of a program may watching a collection for new values and another part is adding new values in it. You need to share the resource and the answer might be pointers and dynamic allocations.

Even then, I would dynamically allocate a class that contains the collection instead of the collection directly. A class with a name and operations convey meaning and exposes precise operations. A simple vector is much more generic and it's meaning must be derived from the code that uses it.

The other reason why it is not usually dynamically allocated is that the only property we need when using collections is their values. The elements inside it, not the identity of the collection itself.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141