Using std::shared_ptr does not allow for compile time evaluation, constexpr, inlining and other optimizations. This is probably due to multithreading safety.
So I experimented with my own simple smart-pointer, just to see what is possible if thread safety is not needed. This is just a minimal draft of an intrusive, linked list reference tracking smart pointer (see the comments in the example for more info).
Questions:
- The example below compiles to full compile time evaluation on clang. On gcc it does not. Why?
- clang does compile time evaluate everything, if, and only if some null pointer checks are included (see
~myptr()). Why? - These null pointer checks are not actually needed, due to the invariant of the smart pointer. Is there an attribute etc. that tells clang to trust the pointer is never null?
https://godbolt.org/z/1xK1r4YjT
#include <memory>
//#include <iostream>
// Simplistic linked list reference tracking smart pointer.
//
// Linking instead of reference counting is interesting because it allows for extra functionality such as
// moving around referenced memory blocks (growable objects, defragmentation), "set null"
// semantics on delete, etc.
//
// Not thread safe, but (trying to) support a high level of compile time evaluation, inlining,
// optimization.
template<typename T>
struct myptr {
private:
T * p;
myptr * next;
public:
constexpr myptr(T * p) noexcept : p(p), next(p ? p->first : nullptr) {
p->first = this;
}
constexpr myptr(const myptr & other) noexcept : myptr(other.p) {}
constexpr T &operator*() {
return *p;
}
constexpr T *operator->() {
return p;
}
T &operator=(const myptr & other) = delete;
T &operator=(const myptr && other) = delete;
constexpr ~myptr() noexcept {
if (p) {
// We must remove `this` from the list. As these are typically stack references, they will be
// mostly LIFO, so we will typically find it immediately.
#if 1
myptr ** pnext = &(p->first);
while (*pnext) {
if (*pnext == this) {
// Found our smart pointer, bypass.
*pnext = next;
break;
}
pnext = &((*pnext)->next);
}
#else
// NOTE: the loop with null check (above) should not be necessary, as the invariant of myptr holds
// that we will always find `this` in the list and never hit a null pointer.
// However, if we use the code below, clang will immediately stop compile time evaluation.
// Is there some "function with potential null pointer fault" restriction in play?
myptr ** pnext = &(p->first);
while (*pnext != this) {
pnext = &((*pnext)->next);
}
// Bypass.
*pnext = next;
#endif
next = nullptr;
if (!p->first) {
// The last reference vanished.
delete p;
}
p = nullptr;
}
}
};
struct Obj {
int i{20};
inline ~Obj() noexcept {
i = -1;
//std::cout << "destructor" << std::endl;
}
private:
// intrusive linked references list
struct myptr<Obj> * first{};
friend struct myptr<Obj>;
};
// Try different pointers:
using obj_ptr = myptr<Obj>;
//using obj_ptr = std::shared_ptr<Obj>;
//using obj_ptr = Obj*;
//constexpr
inline
int fun(obj_ptr obj) {
return obj->i;
}
int main() {
obj_ptr obj(new Obj);
{ obj_ptr obj2(obj);
obj2->i += 1;
}
obj_ptr obj3(obj);
obj3->i *= 2;
return fun(obj);
}