CiaranWelsh
CiaranWelsh

Reputation: 7681

How do smart pointers affect the rule of 5?

I've learnt that when you use pointers in a class, you should implement the rule of 5. If you do not use pointers then you're okay, and in fact its preferable, to use the defaults. However, how does this work with smart pointers? For example a class containing an int* might look like this:

class A {
private:
    int *num_;
public:

    explicit A(int* num) : num_(num) {}

    ~A() {
        delete num_;
    }

    A(const A &other) {
        if (this != &other) {
            num_ = other.num_;
        }
    }

    A(A &&other) noexcept {
        if (this != &other) {
            num_ = other.num_;
        }
    }

    A &operator=(A &other) {
        if (this == &other) {
            this->num_ = other.num_;
        }
        return *this;
    }

    A &operator=(A &&other) noexcept {
        if (this == &other) {
            this->num_ = other.num_;
        }
        return *this;
    };


};

But if we use smart pointers, is it sufficient to just do this?

class B {
private:
    std::unique_ptr<int> num_;

public:

    explicit B(int num) : num_(std::make_unique<int>(num)) {};

};

Upvotes: 3

Views: 1301

Answers (3)

Caleth
Caleth

Reputation: 63142

Those have different behaviour. A can be copied, B can only be moved.

N.B. your implementation of A is unsafe, it can lead to leaks and undefined behaviour.

A comparison of like-for-like would either delete A's copy

class A {
private:
    int *num_;
public:

    explicit A(int num) : num_(new int(num)) {}

    ~A() {
        delete num_;
    }

    A(const A &other) = delete;

    A(A &&other) noexcept 
     : num_(std::exchange(other.num, nullptr)) {}

    A &operator=(const A &other) =delete;

    A &operator=(A &&other) noexcept {
        swap(num_, other.num_);
        return *this;
    };
};

class B {
private:
    std::unique_ptr<int> num_;

public:

    explicit B(int num) : num_(std::make_unique<int>(num)) {};

};

Or define B's copy

class A {
private:
    int *num_;
public:

    explicit A(int num) : num_(new int(num)) {}

    ~A() {
        delete num_;
    }

    A(const A &other) 
     : A(other.num) {}

    A(A &&other) noexcept 
     : num_(std::exchange(other.num, nullptr)) {}

    A &operator=(const A &other) {
        *num_ = *other.num;
        return *this;
    }

    A &operator=(A &&other) noexcept {
        swap(num_, other.num_);
        return *this;
    };
};

class B {
private:
    std::unique_ptr<int> num_;

public:

    explicit B(int num) : num_(std::make_unique<int>(num)) {};
    ~B() = default;
    B(const B & other) : B(*other.num_) {}
    B(B && other) = default;
    B& operator=(const B & other) { *num_ = *other.num_ }
    B& operator=(B && other) = default;

};

Upvotes: 2

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123149

Yes that is sufficent. The unique pointer does manage the memory. However, your two classes will behave differently because std::unique_ptr cannot be copied, hence there will be no compiler generated copy constructor nor assignment for B.

Also note that you implemented all methods for the rule of 5, but not correctly. As mentioned in a comment, copying an A will result in two instances having the same pointer and delete it upon destruction. Actually getting this right is the whole point about the rule of 3/5 and why you should prefer the rule of 0.

Upvotes: 4

Jean-Marc Volle
Jean-Marc Volle

Reputation: 3333

If you use smart pointer (or any of the std:: containers) the class default destructor will call the destructor of the smart pointer (and containers). More on this topic here: Why doesn't the C++ default destructor destroy my objects?

Upvotes: 1

Related Questions