MiddleCodeRareNot answered yet
Write a correct String class with the Rule of Five
Implement a String class that owns a heap-allocated char[] and manages it correctly under copy and move.
Requirements:
- follow the Rule of Five: destructor, copy constructor, copy-assignment, move constructor, move-assignment
- implement copy-assignment via copy-and-swap so self-assignment is safe and assignment is exception-safe
- the move constructor must null the source pointer so the source destructor does not double-free
- account for the null terminator in every size/allocation calculation
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;
// your code here
};
Write the implementation.
String owns a heap char[] and must follow the Rule of Five: destructor, copy ctor, copy-assignment via copy-and-swap, move ctor, move-assignment. Move operations null the source pointer so the source destructor doesn't double-free.
- ✗Forgetting the null terminator in size calculations —
new char[len_ + 1] - ✗Not handling self-assignment in operator= without copy-and-swap
- ✗Implementing move constructor without zeroing the source pointer — the source destructor then double-frees
- →How does the copy-and-swap idiom provide strong exception safety?
- →What changes when you add SSO (small string optimisation)?
Contents
Task
Implement a String class — a simplified std::string analogue:
- owns heap-allocated storage
- implements the Rule of Five
- uses copy-and-swap in the assignment operator
Solution
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cassert>
class String {
public:
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);
}
~String() { delete[] data_; }
String(const String& other)
: len_(other.len_), data_(new char[other.len_ + 1]) {
std::memcpy(data_, other.data_, len_ + 1);
}
// Single operator= handles both copy and move assignment
String& operator=(String other) {
swap(*this, other);
return *this;
}
String(String&& other) noexcept
: data_(other.data_), len_(other.len_) {
other.data_ = nullptr;
other.len_ = 0;
}
[[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_;
};
int main() {
String a("hello");
String b(a);
assert(std::strcmp(b.c_str(), "hello") == 0);
String c("world");
c = a;
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));
assert(std::strcmp(d.c_str(), "hello") == 0);
String e;
e = std::move(d);
assert(std::strcmp(e.c_str(), "hello") == 0);
std::cout << e << '\n';
return 0;
}
Key points
| Aspect | Decision |
|---|---|
| Null terminator | new char[len_ + 1], memcpy copies len_ + 1 bytes |
| Copy-and-swap | Parameter taken by value → compiler makes a copy → swap |
| Self-assignment | Automatically safe: copy is made before the swap |
| Move constructor | Zero out other.data_ → delete[] nullptr is safe |
| Exception safety | If new throws in copy ctor, the original object is untouched |
Contents