Как интеллектуальные указатели влияют на правило пяти?
Я узнал, что, когда вы используете указатели в классе, вы должны реализовать правило 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++ по умолчанию не уничтожает мои объекты?