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.