2

I'm writing a C++ array-like container that can dynamically grow and shrink. I'd like to prevent users of this container from taking the address of its items, because they might be reallocated when the container needs to reallocate itself. The only correct way of using this container will be by keeping track of the address of the container and the index of each item (yes, I'll be specifying it in the documentation, but it would be better if I could make the compiler trigger an error if a user of the container tries to get the address of an item?

Can this be done somehow? I searched and found some question regarding making the "address of operator" private, but it doesn't seem to be guaranteed to work, nor it's a recommended practice either. So, I wonder if there could be any alternative technique for preventing access to pointers to items...

cesss
  • 852
  • 1
  • 6
  • 15
  • Nope. The arcane overload of the address-of operator would apply to the class in the container, and not the container itself. If the objects in the container do not overload their address-of operator, the container can't do anything about that. C++ does not work this way. – Sam Varshavchik Jan 28 '21 at 13:27
  • Does this answer your question? [Preserve access privileges when using '->' operator](https://stackoverflow.com/questions/65850942/preserve-access-privileges-when-using-operator) – D-RAJ Jan 28 '21 at 13:27
  • 2
    As long as you are returning a reference to the contained items, the user will be able to get the address of said items. – NathanOliver Jan 28 '21 at 13:27
  • 1
    As others mentioned, it's not really possible to achieve it unless you'd always return a copy to the container elements, but no one would use such container. There's no really good way that would satisfy everyone to solve that problem, you can include in documentation when elements get relocated and pointers get invalidated, or you could keep elements in `std::shared_ptr` and return as such but it would affect performance by a lot. – Kaldrr Jan 28 '21 at 13:30
  • 1
    *I'd like to prevent users of this container from taking the address of its items* -- You prevent users by putting in your documentation for the container: **Danger. Taking the address of the elements in the container has risks**. If the user does not want to heed what is documented, it's their fault if things do not work out. – PaulMcKenzie Jan 28 '21 at 13:31
  • This also sounds like XY problem, why would your container suddenly rearrange elements and invalidate iterators/pointers to elements? Because of concurrent use? Because it might need to adjust it's internal structure after insertion/removing an element, like `std::set` does? – Kaldrr Jan 28 '21 at 13:36
  • 2
    I suggest to read this [Q&A about iterator invalidation rules](https://stackoverflow.com/questions/6438086/iterator-invalidation-rules). C++ programmers are used to pointers and iterators to elements in containers getting invalidated under certain operations. Inform the user when and which pointers get invalidated, thats more or less all you can do. – 463035818_is_not_an_ai Jan 28 '21 at 14:18
  • `std::vector` has same "problem" and everyone has accepted that. Just document this well. – Marek R Jan 28 '21 at 14:33

2 Answers2

0

In C++ all memory allocated to the program is more or less fair game. So there is no real way to prevent users of your array type to obtain or calculate addresses within your array.

The STL even makes a lot of effort to guarantee that iterators do not suddenly become invalid because of this.

In the real world you write in the API description that it is very unwise to work with addresses within your container because the could become invalid at any time and then it is the responsibility of the users to follow that rule, IMHO.

U. W.
  • 414
  • 2
  • 10
  • The STL indeed tries to not invalidate iterators, but it also documents exactly where it could happen. The "real world" API that states "could become invalid at any time" is utterly unusable. You can't even copy elements out of the container, because the address could become invalid while the copy ctor runs! – MSalters Jan 28 '21 at 13:42
0

What you want to do is not possible easily (if at all). If your container invalidates pointers to elements in certain situations, that does not make your container "special". Lets consider what other containers do to mitigate this problem.

std::vector:

 // THIS CODE IS BROKEN !! DO NOT WRITE CODE LIKE THIS !!
 std::vector<int> x(24,0);

 int* ptr = &x[0];
 auto it = x.begin();

 x.push_back(42);
 x.insert(it, 100);     // *
 *ptr = 5;
 *it = 7;

Starting from the line marked with * everything here uses an invalid pointer or iterator. From cppreference, push_back:

If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated.

Actually x.insert(it, 100); might be using a valid iterator, but the code does not check whether the push_back had to increase capacity, so one has to assume that it and ptr are invalid after the call to push_back.

insert:

Causes reallocation if the new size() is greater than the old capacity(). If the new size() is greater than capacity(), all iterators and references are invalidated. Otherwise, only the iterators and references before the insertion point remain valid. The past-the-end iterator is also invalidated.

Users of standard containers must be aware of iterator invalidation rules (see Iterator invalidation rules) or they will write horribly broken code like the one above.

In general, you cannot protect yourself from all mistakes a user can possibly make. Document pre- and post-conditions and if a user ignores them they just get what they deserve.

Note that you could try to overload the & operator, but there is no way you can prevent someone to get the adress via std::addressof, which is made exactly for that: Get the address of an object in case the object tries to prevent it by overloading &.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185