Search code examples
c++pointersnew-operator

Is there a way to prevent passing "new T" to my observer_ptr constructor?


I want to implement my own observer_ptr class which I want to use to indicate no ownership of a pointer. I'm wondering if it'd be possible to somehow prevent someone from using the following syntax:

observer_ptr<T> var_name(new T);

since it would completely invalidate the whole purpose of this class and lead to memory leaks. My guess would be that I would have to delete/make private a certain type of constructor, but I don't know what argument type I should choose to achieve that.

I'm not writing any sharable library, I'm asking more for myself so I can learn.


Solution

  • If you accept the input T* pointer by a non-const reference, users can't call new directly in the constructor call, as the reference won't be able to bind to the temporary that new returns, eg:

    template <typename T>
    class observer_ptr
    {
    public:
        observer_ptr(T*& ptr);
        ...
    };
    
    observer_ptr<T> var_name(new T); // error!
    

    They would be required to use a local T* variable instead, making them more aware of their responsibility for the lifetime of the object they create, eg:

    T *ptr = new T;
    observer_ptr<T> var_name(ptr); // OK!
    

    Note, however, that this approach does prevent users from being able to directly observe non-dynamic objects whose addresses are taken by operator& or std::addressof(), as well as unique_ptr and shared_ptr objects, eg:

    T obj;
    observer_ptr<T> var_name(&obj); // error!
    
    auto ptr = make_unique<T>(); // or make_shared()
    observer_ptr<T> var_name(ptr.get()); // error!
    

    This would also require the use of a local T* pointer, eg:

    T obj;
    T* ptr = &obj;
    observer_ptr<T> var_name(ptr); // OK!
    
    auto obj = make_unique<T>(); // or make_shared()
    T* ptr = obj.get();
    observer_ptr<T> var_name(ptr); // OK!
    

    You can avoid that if you add some additional constructors to observer_ptr, eg:

    template <typename T>
    class observer_ptr
    {
    private:
        T* m_ptr;
    public:
        observer_ptr(T*& ptr) : m_ptr(ptr) {}
        observer_ptr(T& obj) : m_ptr(addressof(obj)) {}
        observer_ptr(const unique_ptr<T>& ptr) : m_ptr(ptr.get()) {}
        observer_ptr(const shared_ptr<T>& ptr) : m_ptr(ptr.get()) {}
        observer_ptr(unique_ptr<T>&&) = delete;
        observer_ptr(shared_ptr<T>&&) = delete;
        ...
    };
    
    T obj;
    observer_ptr<T> var_name(obj); // OK!
    
    auto ptr = make_unique<T>(); // or make_shared()
    observer_ptr<T> var_name(ptr); // OK!
    
    observer_ptr<T> var_name(make_unique<T>()); // error!
    
    observer_ptr<T> var_name(make_shared<T>()); // error!