4

I'm sorry if this is very simple but I haven't played with C++ for more than 15 years. Consider this simple example: A vector contains objects of type A. An object of class B must reference an A object that resides in the vector. (Edit for clarification - class B must have a member that references the A instance)

Back in the day you would just declare an A* and be done with it, but how would one do it today with smart pointers? I don't want to store shared or unique pointers in the vector, because I don't want the A objects allocated all over the heap. They have to be in the vector itself.

Quentin
  • 62,093
  • 7
  • 131
  • 191
Jesper
  • 331
  • 3
  • 12
  • 2
    What's wrong with the way you did it back in the day? – Kerrek SB Apr 11 '18 at 23:07
  • Good question:) I was just under the impression that raw pointers were being discouraged. – Jesper Apr 11 '18 at 23:09
  • 2
    Raw pointers for memory management (not raw pointers in general) are being discouraged. This series of blogs is a good way to figure out smart pointers: https://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/ – gflegar Apr 11 '18 at 23:11
  • As long as the vector outlives the `B`'s there is nothing wrong using a pointer – NathanOliver Apr 11 '18 at 23:13
  • 3
    "I don't want the A objects allocated all over the heap" objects stored as values in a vector are always allocated on the heap. –  Apr 11 '18 at 23:16
  • Yes I know - but not spread all over it:) Yikes - just realized the problems if elements are erased or the vector must reallocate to grow. Luckily that's not an issue for my particular case – Jesper Apr 11 '18 at 23:22
  • 2
    @Nathan And as long as the vector is not modified. –  Apr 11 '18 at 23:25
  • 4
    No, raw pointers are not discouraged. Manual memory management is discouraged, but that overlaps with raw pointers only in very superficial and uninteresting ways. – Kerrek SB Apr 11 '18 at 23:37
  • 2
    @Jesper "*how would one do it today with smart pointers?*" - smart pointers don't apply in the example you described. Simply declare a raw `A*` pointer, or an `A&` reference, in class `B`, and point/refer it to the desired `A` object. No smart pointer needed, since `B` does not manage the lifetime of the `A` object, the `vector` does. Just make sure the `B` object does not outlive the `A` object. – Remy Lebeau Apr 12 '18 at 00:13
  • 2
    If You are using a vector, can't the index be stored instead of pointer/reference? – Robert Andrzejuk Apr 12 '18 at 01:01
  • 1
    but vector stores your data in the heap internally – Lorence Hernandez Apr 12 '18 at 01:18
  • Robert you're right I can:) Perhaps it can even perform faster than pointers under some circumstances than pointers because it needs less bits for the reference (more cache friendly). I'm planning on testing if I'll be able to observe that with multithreading. Lorence yes but in contiguous memory, and with less redirection (and less memory used because you save a pointer per object) – Jesper Apr 12 '18 at 07:05
  • 1
    @NeilButterworth the values in the vector are allocated wherever the allocator wants, which *by default* is the heap – Caleth Apr 12 '18 at 08:30
  • 2
    If you want to store objects directly in the vector, then I urge you consider using `std::array`. If you want to store any reference to the object in vector, you must remember it will get invalidated upon resize. If you don't want to allow resizing/invalidation within the vector, then it's as good as `array` and this type will express those semantics verbosely. – luk32 Apr 12 '18 at 09:28
  • @luk32 Thanks. This is exactly the semantics I want. The master array - the set of all tiles in a zone - will never grow or shrink after creation. – Jesper Apr 12 '18 at 09:42

3 Answers3

5

You have a few options, based on your requirements.

Non-owning raw-pointer A*

There is nothing wrong with having a non-owning raw-pointer in modern C++. If B needs a nullable reference to A and the A can be guaranteed to outlive B then a raw-pointer is perfectly fine. One reason that the reference might need to be nullable is if you need to default construct B and then set the reference later:

class B {
  A* a_ = nullptr;
public:
  void setA(A& a) { a_ = &a; }
};

int main() {
  std::vector<A> aVec(3);
  B b;
  b.setA(aVec[1]);
}

Reference A&

If the reference does not need to be nullable. If the reference is set in the constructor of B and never changes then you can use a reference A&:

class B {
  A& a_;
public:
  B(A& a) : a_(a) {}
};

int main() {
  std::vector<A> aVec (3);
  B b(aVec[1]);
}

std::reference_wrapper<A>

One problem with using a reference is that you can't reseat the reference to point to a different a which means you can't have a setA member function and you can't have an assignment operator B::operator=(const B&). You could just use a non-owning raw-pointer and enforce that the raw-pointer should never be null. But the standard library now provides a convenience called std::reference_wrapper which can not be null like a reference but can be reseated like a pointer:

class B {
  std::reference_wrapper<A> a_;
public:
  B(A& a) : a_(a) {}
  void setA(A& a) { a_ = a; }
};

int main() {
  std::vector<A> aVec (3);

  B b(aVec[1]);
  B otherB(aVec[2]);

  b = otherB;  // use auto-generated assignment operator
  b.setA(aVec[0]);
}

index to an element in the vector

One common case is where the vector of A is growing and so it might re-allocate and invalidate references, pointers and iterators. In this case you could store an index to an element in the vector. This index will not be invalidated when the vector grows and also you can check the index is within bounds of the vector and not dangling:

class B {
  std::vector<A>& aVec_;
  int             aIndex_;
public:
  B(std::vector<A>& aVec, int aIndex) : aVec_(aVec), aIndex_(aIndex) {}

  void useA() {
    if (aIndex_ >= 0 && aIndex_ < aVec_.size()) {
      auto& a = aVec_[aIndex_];
      // use a ...
    }
  }
};

int main() {
  std::vector<A> aVec (3);

  B b(aVec, 1);
  b.useA();
}

If you are adding and removing from your vector of A then none of these approaches will work and you might need to reconsider your design.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • None of the approaches will work if you invalidate iterators with `vector` storing objects directly within itself. – luk32 Apr 12 '18 at 09:21
  • Thanks. It's going to be either non-owning raw pointer or index then:) And probably std::array as suggested earlier. Did you mean to say that B should be guaranteed NOT to outlive A? And for the index I would use "if (aIndex_ >= 0...", since 0 is a valid tile right? – Jesper Apr 12 '18 at 09:51
  • @Jesper Yes, you are right, I've fixed those two mistakes. – Chris Drew Apr 12 '18 at 09:54
  • 1
    @Jesper `std::array` is probably the right choice if you know the size at compile time. – Chris Drew Apr 12 '18 at 09:57
  • @ChrisDrew ah ok, vector it is then since size is fixed at runtime, not compile time. – Jesper Apr 12 '18 at 10:04
  • 1
    @Jesper there used to be a proposal for `std::dyn_array` which had a constant size determined at runtime, but it seems to have been abandoned. I’d look in Boost to see if it had something like that before deciding to use `vector`. – Daniel H Apr 15 '18 at 17:22
  • I didn’t find anything in Boost, but it looks like [Abseil](https://abseil.io/about/) has a `FixedArray`. – Daniel H Apr 15 '18 at 17:31
3

The primary purpose of smart-pointers is to manage life-time.
An observer (i.e. non owning pointer) can still be a raw pointer.

sp2danny
  • 7,488
  • 3
  • 31
  • 53
1

The obvious answer is the simplest, store the index of the object, not the address.

Tiger4Hire
  • 1,065
  • 5
  • 11
  • That is definitely something I would really like to do if my benchmarking suggests it's feasible performance-wide. If I can do this throughout an entire subsystem then it would be possible to save the entire state by using a custom allocator and just save the entire memory block - no need for traversing anything to hook up pointers correctly on deserializing. – Jesper Apr 13 '18 at 11:56
  • If your on Windows, try profiling an AMP version too. https://msdn.microsoft.com/en-us/library/hh265137.aspx. An index-oriented version of the code should be a doddle to convert. I'd love to see the results. – Tiger4Hire Apr 13 '18 at 13:10
  • https://stackoverflow.com/questions/8145449/how-come-my-array-index-is-faster-than-pointer – Tiger4Hire Apr 13 '18 at 13:21