Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with implementation of unique_ptr's move constructor

I'm trying to write a unique_ptr implementation. I'm struggling with writing a move constructor. Here are my problems:

  1. When I mark the move constructor as default, my resource is deleted twice, when I move assign a pointer (auto foo2 = std::move(foo); below) - why?
  2. When I'm trying to assign the underlying pointer in the move constructor like this *rhs = nullptr (see implementation below), the compiler says *rhs is an rvalue and that I cannot assign anything to it.
  3. Finally, rhs.m_ptr = nullptr works. Why does it work, when *rhs = nullptr doesn't?

My code:

#include <iostream>

namespace my
{
template <class T>
class unique_ptr
{
public:
    unique_ptr()
    {
        m_ptr = new T;
    }
    unique_ptr(const unique_ptr&) = delete;
    // move constructor
    unique_ptr(unique_ptr&& rhs)  // = default deletes m_ptr twice
    {
        m_ptr = *rhs;
        rhs.m_ptr = nullptr;  // *rhs = nullptr doesn't work (*rhs is an rvalue)
    }
    ~unique_ptr()
    {
        delete m_ptr;
    }
    T* operator->()
    {
        return m_ptr;
    }
    T* operator*()
    {
        return m_ptr;
    }
    unique_ptr& operator=(const unique_ptr&) = delete;
    // no move assignment yet
private:
    T* m_ptr;
};

}  // namespace my

struct Foo
{
    Foo()
    {
        std::cout << "Foo" << std::endl;
    }
    ~Foo()
    {
        std::cout << "~Foo" << std::endl;
    }
    void printHello()
    {
        std::cout << "Hello" << std::endl;
    }
};

int main()
{
    my::unique_ptr<Foo> foo;
    foo->printHello();

    auto foo2 = std::move(foo);

    return 0;
}

On a side note, apparently I can pass a unique_ptr without any template parameter to methods inside the unique_ptr class template. Does compiler just assume it's T?

Please discard any other implementation faults that don't relate to the described problems. It's work in progress.

like image 651
user6646922 Avatar asked Feb 01 '26 23:02

user6646922


1 Answers

1) The default move constructor doesn't know about the semantics of your class. So it moves the pointer rhs, but it will not reset the other pointer, which will get deleted as well in the other destructor.

2) *rhs calls operator* and returns a temporary/rvalue T*, a copy of the internal pointer, and is not consistent with the usual operator* which should return a T& or a const T&.

3) see 2. you are returning a temporary object.

So finally, what you should have:

unique_ptr(unique_ptr&& rhs)  // = default deletes m_ptr twice
: m_ptr(rhs.m_ptr)
{
    rhs.m_ptr = nullptr;  // *rhs = nullptr doesn't work (*rhs is an rvalue)
}

T& operator*() {return *m_ptr;}
const T& operator*() const {return *m_ptr;}

And so on.

like image 118
Matthieu Brucher Avatar answered Feb 03 '26 13:02

Matthieu Brucher



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!