Как интеллектуальные указатели влияют на правило пяти?

Я узнал, что, когда вы используете указатели в классе, вы должны реализовать правило 5. Если вы не используете указатели, тогда все в порядке, и на самом деле это предпочтительнее использовать значения по умолчанию. Однако как это работает с интеллектуальными указателями? Например, класс, содержащийint* может выглядеть так:

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;
    };


};

Но если мы используем интеллектуальные указатели, достаточно ли этого просто сделать?

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

public:

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

};

3 ответа

Решение

Да, этого достаточно. Уникальный указатель действительно управляет памятью. Однако ваши два класса будут вести себя по-разному, потому что std::unique_ptr не может быть скопирован, следовательно, не будет ни созданного компилятором конструктора копирования, ни присваивания для B.

Также обратите внимание, что вы реализовали все методы для правила 5, но не правильно. Как упоминалось в комментарии, копированиеAприведет к тому, что два экземпляра будут иметь один и тот же указатель и удалит его при уничтожении. Собственно, правильно ли вы это сделаете, и в этом весь смысл правила 3/5 и того, почему вам следует предпочесть правило 0.

У них другое поведение. A можно скопировать, B можно только перемещать.

NB ваша реализация A небезопасно, это может привести к утечкам и неопределенному поведению.

Сравнение аналога либо delete Aкопия

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)) {};

};

Или определите Bкопия

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;

};

Если вы используете интеллектуальный указатель (или любой из std:: container), деструктор класса по умолчанию вызовет деструктор интеллектуального указателя (и контейнеров). Подробнее по этой теме здесь: Почему деструктор C++ по умолчанию не уничтожает мои объекты?

Другие вопросы по тегам