1 year ago
#336455
Ben
Understanding UB and pointer-arithmetic
I have a generic reference-counted heap-allocated wrapper class. So my class is basically just a pointer:
template <typename T>
class Refcounted {
struct model {
std::atomic<std::size_t> count{1};
T value;
};
model* m_self;
public:
...
Refcounted(const Refcounted& other) : m_self(other.m_self) {
assert(m_self);
++m_self->count;
}
...
T& operator*() { return m_model->value; }
};
Now, I'd like to be able to get a T&
even in a context where T
is forward-declared. As I understand it, I can't, because if T
is just forward-declared, it can't know the layout of model
(in particular, it can't know the offset of value
because it can't know T
's alignment).
I believe that if I swapped the order of model
, it would be well-defined behavior to reinterpret_cast
, correct?:
template <typename T>
class Refcounted {
struct model {
T value;
std::atomic<std::size_t> count{1};
};
model* m_self;
public:
Refcounted(T&& x) : m_self(new model(std::move(x))) {
static_assert(offsetof(model, value) == 0, "Using this assumption below to reinterpret_cast");
}
...
Refcounted(const Refcounted& other) : m_self(other.m_self) {
assert(m_self);
++m_self->count;
}
...
T& operator*() { return *reinterpret_cast<T*>(m_model); }
};
Assuming that's correct, great... but now the copy-constructor requires T
to be defined because it needs to find m_self->count
. I had a thought for dealing with that, but I suspect it's UB: If I set up the model
struct so that std::atomic<std::size_t> count
is first and there's no padding between it and the T
, then Refcounted
keeps a void*
pointer to the value
field, as in m_valptr{&(new model(std::move(x)))->value}
, then I could reinterpret_cast<T*>(m_valptr)
to get at the value
(which I think (?) is still well-defined-behavior). Is there any defined way to go from that pointer to a pointer to count
? In principal, it's just decrementing the pointer by std::atomic<std::size_t>
, but I suspect it breaks rules that I don't fully understand about what can and cannot be done with pointers.
I could add a second pointer to Refcounted
or I could make model
use a virtual interface, but that adds overhead. I feel like this should be possible but that there are spooky language rules getting in the way.
c++
undefined-behavior
pointer-arithmetic
reinterpret-cast
0 Answers
Your Answer