1

I am attempting to understand the internals of std::shared_ptr by writing my own (VERY BASIC) implementation. What I have so far is the following (with most of the operators and other code removed).

template <typename T>
struct ControlBlock {
    unsigned int refCount;
    T* ptr;
};

template <typename T>
class MyPointer {
    //...
private:
    ControlBlock<T>* _cb;
};

Everything has been working fine so far except when I try to do the following:

MyPointer<Foo> ptr1;
MyPointer<const Foo> ptr2 = ptr1;

In this case, the non-const pointer cannot be converted to the const pointer because they are two completely different types as far as the template expansion is concerned.

How do I write the pointer in such a way that it allows this (implicit) conversion?

EDIT

I know know from the comments that the pointer should be better implemented as:

struct ControlBlock {
    unsigned int refCount;
};

template <typename T>
class MyPointer {
private:
    T* _ptr;
    ControlBlock* _ctrl;
};

Ultimately, I would like to use this with my own memory pool which will allocate the following:

template <typename T>
struct ControlBlockWrapper {
    ControlBlock ctrl;
    T value;
};

If my control block contains a pointer back to my pool, I am very confused how I can get from from T* and ControlBlock* (stored by the pointer) to a pointer to the real ControlBlockWrapper that will need to be deleted from the pool. For example, since my pool is creating ControlBlockWrapper's, it's delete method would look something like:

template <typename T>
class Pool {
    //...
    void destory(ControlBlockWrapper<T>* ptr);
};

Pointer arithmetic seems like a poor way to accomplish this, but I'm not sure how else to do it.

Patrick Wright
  • 1,401
  • 7
  • 13
  • 1
    `MyPointer` has to have a templated constructor or conversion operator to itself (but with a different template argument, hence templated). To make it work, you'll need to store two separate pointers: to control block and to the object. Also the control block will have to store the deleter. This will ultimately allow you to have a pointer to a subobject, which shares ownership to the whole object. – HolyBlackCat Jul 25 '22 at 17:39
  • @HolyBlackCat Since The control block and data pointers are private members of essentially a different type (one with and one without const), is it required that I make all MyPointer's friends of themselves? – Patrick Wright Jul 25 '22 at 19:56
  • @HolyBlackCat I just found my answer: https://stackoverflow.com/questions/25182550/template-class-friendship – Patrick Wright Jul 25 '22 at 19:56
  • Either friends, or make a constructor flexible enough to set them. Friendship sounds better, because it can't be misused by your user. – HolyBlackCat Jul 25 '22 at 19:58
  • @HolyBlackCat See my edit. I'm guessing this has something to do with a "deleter." – Patrick Wright Jul 25 '22 at 20:50
  • For extra credit you need to implement `weak_ptr`. It's essentially a second counter that doesn't prevent the object (`T`) being destructed but the control block is retained until all the weak and 'strong' (sharing) pointers are. – Persixty Jul 25 '22 at 21:09

1 Answers1

1

How to implement cast for "smart pointer" non-const to const

Implement exactly that - a conversion operator from non-const to const. With help of how can I use std::enable_if in a conversion operator? to do some SFINAE:

#include <iostream>
#include <type_traits>

template <typename T>
struct ControlBlock {
    unsigned int refCount;
    T* ptr;
};

template <typename T>
struct MyPointer {
    template<
        typename T2 = T,
        typename = typename std::enable_if_t<std::is_same_v<T2, std::remove_const_t<T2>>>
    >
    operator MyPointer<const T2>() {
        // do some magic conversion stuff here
        MyPointer<const T2> ret{nullptr};
        return ret;
    }

    ControlBlock<T>* _cb;
};

class Foo;

int main() {
    MyPointer<Foo> ptr1;
    MyPointer<const Foo> ptr2 = ptr1;
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111