Reimundo Heluani
Reimundo Heluani

Reputation: 978

C++ several instances (but not all) of a class sharing a vector

I want to have a class C, such that instances of C hold a std::vector<int> as a private member. Now many instances (but not all) of C will share that vector and keeping a copy for each instance is what I would want to avoid.

If this vector were a single integer for example, I could create a template class for C and have it like this:

template <int N>
class C {
 // something
};

If instead of some instances I wanted all instances to share that vector then I would declare the variable static as

class C {
    static std::vector<int> _vec;

What I am looking is for something intermediate. I could for example keep a shared_ptr as a private non-static member as in

class C {
     std::shared_ptr<std::vector<int>> _vec;
}

And that would take care of the lifetime of the object, with some overhead for maintaining shared ownership and also an extra pointer per-instance. I could keep a reference instead and let the creator of the instances take care of the lifetime, but avoid the overhead of the shared_ptr. Is there a better approach to this design question?

Upvotes: 0

Views: 75

Answers (1)

Marcus M&#252;ller
Marcus M&#252;ller

Reputation: 36346

…such that instances of C hold a std::vector as a private member. Now many instances (but not all) of C will share that vector

So, that's self-contradicting. You can't hold an instance and share the instance safely.

This is a classical use case of pointers; even more specifically, classical use case of a shared pointer, as you already noticed.

I could keep a reference instead and let the creator of the instances take care of the lifetime,

/me sounds the access-after-destruction alarm
You're about to find out that won't work. The lifetime is not like you think, but ends when the owning object's lifetime ends. So, this breaks. (Also, you'll find that under the hood, handing out references compiles down to the same code as handing out raw pointers – it's just safer and does the dereferencing automagically within the language; you're not actually saving a memory indirection there. The classes containing the reference still need to know the address of the object they're handling, and that's the same as having a pointer, if you ask your CPU.)

It breaks, unless the thing handing out the references keeps a count on how many references it handed out, subtracts many references it handed out belong to C instances that were deconstructed, and then deconstructs the vector as soon as that number reaches 0. Take a wild guess what a shared_ptr does! Exactly this reference counting.

Often, however, the access logic is easier; for example,

I have a vector of C, std::vector<C> lexicon, and all held C._vec need to get removed exactly when that full lexicon goes out of scope, so I handle that manually

actually doesn't need reference counting and hence is lower-overhead. You can indeed solve that by handing out references – but I'd actually prefer to hand out raw pointers in that case: they just as much don't imply ownership, and you don't accidentally make a copy of the std::vector<int> when you pass the pointer by value.

If your lifetime tracking isn't as easy as that example :

There's no magic usage tracking in references; and you need something to keep track of whether something is still holding some handle to your individual vector. That tracking is exactly the "overhead" shared_ptr incurs. No way around it.

A private shared_ptr is hence the right approach:

class C {
  private:
    std::shared_ptr<std::vector<int>> _vec;
}

Your static approach takes you nowhere here - it only makes sense if all, and really all, instances share the same vector which should essentially live from the beginning of time till the end, and even then, it's a bit of a solution that can lead to additional headaches in form of the static initialization order fiasco. I'd advise to not do that under almost all circumstances.

(Plus, a static member also just boils down to the code looking up the same address – so, performance-wise, same as a pointer. Again, nothing really won here.)

Upvotes: 2

Related Questions