ptr_to() is less flexible than shared_ptr style aliased constructors.
ahmedcharles opened this issue · comments
If I have the following code:
#include <memory>
#include <vector>
struct S {
std::vector<int> v;
};
int main() {
auto p = std::make_shared<S>();
p->v.push_back(1);
auto a = std::shared_ptr<int>(p, &p->v[0]);
}
It's valid and works with shared_ptr, but is currently not allowed by ptr_to. I.e. they do not expose equivalent functionality.
link to ptr_to: https://github.com/hsutter/gcpp/blob/master/deferred_heap.h#L487
That's a legitimate point. I still think ptr_to
handles the large majority of cases, and handles the common case better because it's type-safe.
But it may well be a good idea to support the full flexibility of the shared_ptr
aliasing constructor... after all, C++ is the "you can always open the hood and take control" language. My first thought would be to provide it as an additional function with a name like unsafe_ptr_to()
or reinterpret_cast_ptr_to()
. For example:
auto p = heap.make<S>();
p->v.push_back(1);
auto a = p.reinterpret_cast_ptr_to(p, &p->v[0]);
That way, the 99% case is convenient and type-safe by construction, but in the 1% case you can perform the operation but have to document it using a word like "unsafe" or "reinterpret" in the source code to call extra attention to the line during code review or when an actual problem occurs.
How does that sound?
Edited to add: Nope, that doesn't work. The reason is that the resulting pointer is not guaranteed to be into the deferred_heap
(though we could perform that check dynamically). In this case, it would not be because the vector
's contents are not in heap
, so there's no easy way to keep the right object alive. Just like shared_ptr
's aliasing constructor works because it keeps a pointer to the original reference count block and can keep that alive, doing it here would require the deferred_ptr
to store an extra pointer to the actual allocation it was generated from (above, the allocation pointed into by p
) to keep that alive, in addition to the pointer to the subobject. So we could do it, by having unsafe_ptr_to
return a different type of deferred_ptr
such as a member_deferred_ptr
-- in some ways it's like a pointer to member -- that is one pointer bigger and could do this correctly including that it could point to an object outside in the deferred_heap
.
shared_ptr
works in this case because it contains two pointers which are used for orthogonal things, one for memory management and one for dereferencing and they are never mixed.
Currently, deferred_ptr
only has one stored pointer which is used for both purposes and therefore, there are additional restrictions.
I suppose that the most important decision is what the data layout of deferred_ptr
should be and whether there should be a single type or multiple. Having a single type with two pointers (removing the deferred_heap
pointer) seems appealing to me and having a pointer within deferred_ptr
that never changes where it points to, may simplify the design of the mark/sweep implementation (I'm not sure about this though).
Thanks again for this suggestion. For now I'm waiting for feedback and bug reports from actual use of the library, and deferring enhancements and optimizations until then but keeping them in the backlog.