MiddleКодРедкоЕщё не отвечали
Напишите корректный класс String по Правилу пяти
Реализуйте класс String, который владеет char[] в куче и корректно управляет им при копировании и перемещении.
Требования:
- следуйте Правилу пяти: деструктор, конструктор копирования, copy-assignment, конструктор перемещения, move-assignment
- реализуйте copy-assignment через copy-and-swap, чтобы самоприсваивание было безопасным, а присваивание — exception-safe
- конструктор перемещения должен обнулить указатель источника, чтобы деструктор источника не сделал double-free
- учитывайте нулевой терминатор в каждом расчёте размера/выделения
class String {
public:
String();
explicit String(const char* s);
~String();
String(const String& other);
String& operator=(String other); // copy-and-swap
String(String&& other) noexcept;
std::size_t size() const;
const char* c_str() const;
// ваш код здесь
};
Допишите реализацию.
String владеет char[] в куче и следует Правилу пяти: деструктор, copy-ctor, copy-assignment через copy-and-swap, move-ctor, move-assignment. Move обнуляет указатель источника против double-free.
- ✗Забывать нулевой терминатор при вычислении размера —
new char[len_ + 1] - ✗Не обрабатывать самоприсваивание в operator= без copy-and-swap
- ✗Реализовывать конструктор перемещения без обнуления указателя источника — деструктор источника тогда выполняет double-free
- →Как idiom copy-and-swap обеспечивает строгую гарантию безопасности исключений?
- →Что меняется при добавлении SSO (small string optimisation)?
Оглавление
Задача
Реализуйте класс String — упрощённый аналог std::string:
- хранит строку на куче
- реализует Правило пяти
- использует copy-and-swap в операторе присваивания
Решение
#include <algorithm> // std::copy, std::swap
#include <cstring>
#include <iostream>
#include <cassert>
class String {
public:
// ----- Construction -----
String() : data_(new char[1]{'\0'}), len_(0) {}
explicit String(const char* s) {
len_ = std::strlen(s);
data_ = new char[len_ + 1];
std::memcpy(data_, s, len_ + 1);
}
// ----- Rule of Five -----
// 1. Destructor
~String() { delete[] data_; }
// 2. Copy constructor
String(const String& other)
: len_(other.len_), data_(new char[other.len_ + 1]) {
std::memcpy(data_, other.data_, len_ + 1);
}
// 3. Copy-assignment (copy-and-swap)
String& operator=(String other) { // parameter is already a copy
swap(*this, other);
return *this;
}
// 4. Move constructor
String(String&& other) noexcept
: data_(other.data_), len_(other.len_) {
other.data_ = nullptr;
other.len_ = 0;
}
// 5. Move-assignment — reuse copy-and-swap (parameter binds to rvalue)
// Already handled by the single operator=(String) above.
// ----- Interface -----
[[nodiscard]] std::size_t size() const { return len_; }
[[nodiscard]] const char* c_str() const { return data_; }
friend void swap(String& a, String& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.len_, b.len_);
}
friend std::ostream& operator<<(std::ostream& os, const String& s) {
return os << s.data_;
}
private:
char* data_;
std::size_t len_;
};
// --- tests ---
int main() {
String a("hello");
String b(a); // copy ctor
assert(std::strcmp(b.c_str(), "hello") == 0);
String c("world");
c = a; // copy assign (self-assign safe)
assert(std::strcmp(c.c_str(), "hello") == 0);
a = a; // self-assignment — must not crash
assert(std::strcmp(a.c_str(), "hello") == 0);
String d(std::move(c)); // move ctor
assert(std::strcmp(d.c_str(), "hello") == 0);
String e;
e = std::move(d); // move assign
assert(std::strcmp(e.c_str(), "hello") == 0);
std::cout << e << '\n'; // hello
return 0;
}
Ключевые моменты
| Аспект | Решение |
|---|---|
| Нулевой терминатор | new char[len_ + 1], memcpy с len_ + 1 байт |
| Copy-and-swap | Принимаем параметр по значению → компилятор делает копию → swap |
| Self-assign | Автоматически безопасно: копия создаётся до swap |
| Move constructor | Обнуляем other.data_ → delete[] nullptr безопасен |
| Exception safety | Если new бросает в copy ctor, оригинальный объект не тронут |
Оглавление