-2

When a move happens, usually a class sets the other's pointer to null, and this is the case with std::unique_ptr. The assignment operator is templated so that you can move unique_ptrs to different types (i.e derived classes).

My question is: How can it access the ptr in the other class, a private member, given that it's an instantiated templated class, and therefore different, and can't access private or even protected members?

template <typename T>
class MyPointer
{
public:
    template <typename U>
    void operator=(const MyPointer<U>& other) = delete;

    template <typename U>
    void operator=(MyPointer<U>&& other)
    {
        // WON'T COMPILE, THIS IS A DIFFERENT CLASS FROM THE ARGUMENT
        // CANNOT ACCESS PRIVATE MEMBER 
        other.pointer = nullptr;
        
    }
    char* get() const { return pointer; }

private:
    char* pointer;
};

int main()
{
    struct B {};
    struct D : B {};

    std::unique_ptr<B> base;
    std::unique_ptr<D> derived;

    // std::unique_ptr somehow sets the other pointer to null when moving
    base = std::move(derived);

    MyPointer<B> my_pointer_b;
    MyPointer<D> my_pointer_d;

    // CANNOT ACCESS PRIVATE MEMBER
    my_pointer_b = std::move(my_pointer_d); 
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
Zebrafish
  • 11,682
  • 3
  • 43
  • 119

1 Answers1

1

My question is how can it access the ptr in the other class, a private member, given that it's an instantiated templated class, and therefore different, and can't access private or even protected members?

As others mentioned, you are missing the obvious detail. From the cppreference.com the move assignment std::unique_ptr::operator= explains, how this has been handled for the std::unique_ptr<T, Deleter>(emphasis mine):

unique_ptr& operator=( unique_ptr&& r ) noexcept;// (1) (constexpr since C++23)

[...]

1) Move assignment operator. Transfers ownership from r to *this as if by calling reset(r.release()) followed by an assignment of get_deleter() from std::forward<Deleter>(r.get_deleter()).

[...]

You could also rework your MyPointer the same:

template <typename T, typename Deleter = std::default_delete<T>>
class MyPointer
{
public:
    // ... other members

    template <typename U, typename OtherDeleter>
    MyPointer& operator=(MyPointer<U, OtherDeleter>&& other) noexcept
    {
        // Just like the std::unique_ptr does!
        reset(other.release());
        deleter = std::forward<OtherDeleter>(other.get_deleter());
        return *this;
    }

    T* release() noexcept
    {
        T* releasedPtr = pointer;
        pointer = nullptr;
        return releasedPtr;
    }

    void reset(T* newPtr = nullptr) noexcept
    {
        if (pointer != newPtr) 
        {
            deleter(pointer);
            pointer = newPtr;
        }
    }

    template<typename Self> // requires C++23 support !!
    decltype(auto) get_deleter(this Self&& self) noexcept
    {
        return std::forward<Self>(self).deleter;
    }

private:
    T* pointer{ nullptr}; // instead of char* (maybe ?)
    Deleter deleter;
};

Alternatively, you could make the MyPointer class be friend of the other.

template <typename T> class MyPointer 
{
public:
    // .... other members!
private:
    T* pointer;

    // Declare MyPointer<T> as a friend of MyPointer<U>
    template <typename U>
    friend class MyPointer;  
};

This is less recommended, due to the fact, it spoils the idea of encapsulation, and it can be considered as a misuse of friend declaration.

JeJo
  • 30,635
  • 6
  • 49
  • 88