SmartPtr With Move Semantics

Overview

  • Continue From SmartPtr With Explicit move()

  • Define “Return From Function Moves” requirement

Starting Point

#include "sensors.h"
#include <gtest/gtest.h>

template <typename T> class SmartPtr
{
public:
    SmartPtr() : _p(nullptr) {}
    SmartPtr(T* p) : _p(p) {}
    ~SmartPtr() { delete _p; }

    SmartPtr(const SmartPtr&) = delete;
    SmartPtr& operator=(const SmartPtr&) = delete;

    T* operator->() { return _p; }
    const T* operator->() const { return _p; }
    T& operator*() { return *_p; }
    const T& operator*() const { return *_p; }

    const T* get() const { return _p; }

    void move(SmartPtr& other)
    {
        delete _p;
        _p = other._p;
        other._p = nullptr;
    }

private:
    T* _p;
};

TEST(handwritten_suite, explicit_move)
{
    SmartPtr<Sensor> s1{new ConstantSensor{20}};
    SmartPtr<Sensor> s2;
    
    s2.move(s1);

    ASSERT_EQ(s1.get(), nullptr);
    ASSERT_DOUBLE_EQ(s2->get_temperature(), 20);
}

Return SmartPtr Object From Function

SmartPtr<Sensor> create_constant_sensor(double temperature)
{
    SmartPtr<Sensor> sp{new ConstantSensor{36.5}};
    return sp;
}
  • SmartPtr has copy delete d

  • ⟶ error

  • But: compiler can prove that sp is unused after return

⟶ Implement move constructor

#include "sensors.h"
#include <gtest/gtest.h>

template <typename T> class SmartPtr
{
public:
    SmartPtr() : _p(nullptr) {}
    SmartPtr(T* p) : _p(p) {}
    ~SmartPtr() { delete _p; }

    SmartPtr(SmartPtr&& from)
    {
        delete _p;

        // think about atomic-exchange though, maybe
        _p = from._p;
        from._p = nullptr;
    }

    SmartPtr(const SmartPtr&) = delete;
    SmartPtr& operator=(const SmartPtr&) = delete;

    T* operator->() { return _p; }
    const T* operator->() const { return _p; }
    T& operator*() { return *_p; }
    const T& operator*() const { return *_p; }

    const T* get() const { return _p; }

private:
    T* _p;
};


SmartPtr<Sensor> create_constant_sensor(double temperature)
{
    SmartPtr<Sensor> sp{new ConstantSensor{36.5}};
    return sp;
}

TEST(handwritten_suite, return_by_copy)
{
    auto a_sensor = create_constant_sensor(36.5);
    ASSERT_FLOAT_EQ(a_sensor->get_temperature(), 36.5);
}

Assign SmartPtr Object ⟶ Moved

TEST(handwritten_suite, assignment)
{
    SmartPtr<Sensor> a_sensor{new ConstantSensor{36.5}};
    SmartPtr<Sensor> another_sensor;
    another_sensor = a_sensor;
}
  • SmartPtr has assignment operator delete d

  • ⟶ error

Attempt #1: implement move assignment

Put correct SmartPtr implementation (see below), but fail to std::move() the assignee ⟶ error

Attempt #2: move

#include "sensors.h"
#include <gtest/gtest.h>

template <typename T> class SmartPtr
{
public:
    SmartPtr() : _p(nullptr) {}
    SmartPtr(T* p) : _p(p) {}
    ~SmartPtr() { delete _p; }

    SmartPtr(SmartPtr&& from)
    {
        _p = from._p;
        from._p = nullptr;
    }

    SmartPtr& operator=(SmartPtr&& from)
    {
        // much like self-assignment check, but more dubious
        if (this != &from) {
            delete _p;
            _p = from._p;
            from._p = nullptr;
        }
        return *this;
    }

    SmartPtr(const SmartPtr&) = delete;
    SmartPtr& operator=(const SmartPtr&) = delete;

    T* operator->() { return _p; }
    const T* operator->() const { return _p; }
    T& operator*() { return *_p; }
    const T& operator*() const { return *_p; }

    const T* get() const { return _p; }

private:
    T* _p;
};



TEST(handwritten_suite, assignment)
{
    SmartPtr<Sensor> a_sensor{new ConstantSensor{36.5}};
    SmartPtr<Sensor> another_sensor;
    another_sensor = std::move(a_sensor);
    ASSERT_EQ(a_sensor.get(), nullptr);
}