22

Suppose we have some class, say class Apple, and wanted to store a std::vector containing Apple.

Should the vector contain pointers, references, or plain objects?

std::vector<Apple*> vector; // Definitely won't copy the Apple on lookup, 
    // but I would prefer not to use pointers if I don't have to
std::vector<Apple&> vector; // Seems circuitous?
std::vector<Apple> vector; // I don't know if this copies the Apple on each lookup

My goal is to make it so when I call

Apple& a = vector[4];

the Apple won't get copied.

I've been using C++ for over a year and I've always worked around this and never understood it. Is there a simple explanation of why one of these three approaches is the best practice?

ATOMP
  • 1,311
  • 10
  • 29
  • 10
    I'll ask a question back: who shall *own* the objects you can access by index? The vector? Or will the vector *point* or *reference* to another actual owner? – Zeta Dec 24 '18 at 18:07
  • 4
    Also, is Apple polymorphic? – HolyBlackCat Dec 24 '18 at 18:08
  • Zeta: good question, the vector is going to own the objects. – ATOMP Dec 24 '18 at 18:08
  • 4
    HolyBlackCat: that's a good question, Apple is not polymorphic. But if it was, we'd definitely want to use a pointer, right? – ATOMP Dec 24 '18 at 18:09
  • 6
    @HolyBlackCat: I think so. They sell hardware and software. – Rudy Velthuis Dec 24 '18 at 18:10
  • 2
    If it was polymorphic, I'd suggest figure out how to make a copyable `unique_ptr` wrapper (using type erasure) and use that. If it sounds too hard, use pointers. – HolyBlackCat Dec 24 '18 at 18:11
  • In this case the template class is not polymorphic, but in the future when it is, I'll definitely use the smart pointer wrapper! – ATOMP Dec 24 '18 at 18:13
  • 1
    It depends on how you intend to use it. If there was only one answer there would be only one way. – Galik Dec 24 '18 at 18:15
  • 2
    `std::vector` was designed with a whole-part semantics in mind, i.e., assuming the container should own its elements. If `Apple` is not run-time polymorphic, then `std::vector` is the best choice. – Ilio Catallo Dec 24 '18 at 18:16
  • 1
    At a meta-level, it depends on whether Apple is a value object, or an identity object, or a service object. And it also depends on what owns Apple. Does the vector own Apple? Does the vector just refer to an extant Apple? What is the lifecycle and lifetime of an Apple? – Eljay Dec 24 '18 at 18:16
  • 1
    What is your real concern here about the copy? Performance? If `Apple` is a struct of three integers, please stop concerning yourself with performance and copy away. – screwnut Dec 24 '18 at 21:27
  • https://stackoverflow.com/a/23488449/186997 may add some criteria to help you decide (shameless plug). – utnapistim Dec 24 '18 at 21:46
  • Best practice for what? How are you going to use the vector? – user202729 Dec 25 '18 at 04:18

2 Answers2

20

Use the type T. Remember that operator[] returns a (const) reference, so your Apple& works fine there:

      T& vector<T>::operator[](size_t);
const T& vector<T>::operator[](size_t) const;

That's why you can use vec[3].make_older() to begin with if return_type Apple::make_older(void) exists.

However, keep in mind that there are many methods that invalidate references, so

Apple& reference = vec[3];
vec.push_back(other_apple);

reference.get_eaten();

might result in undefined behaviour if push_back reallocated the referenced apple.

Zeta
  • 103,620
  • 13
  • 194
  • 236
11

Use:

std::vector<Apple> vector; // I don't know if this copies the Apple on each lookup

While all of them would achieve your goal (a lookup never copies), this is the one that allows the vector to own the Apples in it so that the lifetimes are sensibly managed.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278