C++ Primer 第13章:拷贝控制

C++ Primer 第13章:拷贝控制


13.1 拷贝、赋值与销毁

13.1.1 拷贝控制成员概述

复制代码
拷贝控制的五个特殊成员函数(三/五法则):
┌─────────────────────────────────────────────────────┐
│ 1. 拷贝构造函数    T(const T&)                       │
│ 2. 拷贝赋值运算符  T& operator=(const T&)            │
│ 3. 析构函数        ~T()                              │
│ ─────────────────────────────────────────────────── │
│ 4. 移动构造函数    T(T&&)              (C++11)      │
│ 5. 移动赋值运算符  T& operator=(T&&)   (C++11)      │
└─────────────────────────────────────────────────────┘

三法则:如果需要自定义析构函数,通常也需要自定义拷贝构造和拷贝赋值
五法则:C++11 中,如果定义了移动操作,通常也需要定义所有五个

13.1.2 拷贝构造函数

cpp 复制代码
// copy_constructor.cpp -- 拷贝构造函数
#include <iostream>
#include <string>

class MyString
{
private:
    char* data_;
    size_t size_;

public:
    // 普通构造函数
    MyString(const char* s = "")
    {
        size_ = strlen(s);
        data_ = new char[size_ + 1];
        strcpy(data_, s);
        std::cout << "[构造] \"" << data_ << "\"" << std::endl;
    }

    // 拷贝构造函数:参数必须是 const 引用
    // 如果不是引用,会无限递归!
    MyString(const MyString& other)
        : size_(other.size_)
    {
        data_ = new char[size_ + 1];   // 深拷贝:分配新内存
        strcpy(data_, other.data_);
        std::cout << "[拷贝构造] \"" << data_ << "\"" << std::endl;
    }

    ~MyString()
    {
        std::cout << "[析构] \"" << data_ << "\"" << std::endl;
        delete[] data_;
    }

    void show() const { std::cout << data_ << std::endl; }
};

// 拷贝构造函数的调用时机:
// 1. 用一个对象初始化另一个对象
// 2. 将对象作为实参传递给非引用类型的形参
// 3. 从函数返回非引用类型的对象
// 4. 用花括号列表初始化数组或聚合类的成员

void func(MyString s)   // 传值:调用拷贝构造
{
    s.show();
}

MyString makeString()   // 返回值:可能调用拷贝构造(或被优化)
{
    MyString s("temp");
    return s;
}

int main()
{
    using namespace std;

    MyString s1("Hello");

    // 1. 直接初始化
    MyString s2(s1);       // 拷贝构造
    MyString s3 = s1;      // 拷贝构造(不是赋值!)

    // 2. 传值参数
    func(s1);              // 拷贝构造

    // 3. 函数返回值
    MyString s4 = makeString();   // 可能被 RVO 优化

    return 0;
}

13.1.3 拷贝赋值运算符

cpp 复制代码
// copy_assignment.cpp -- 拷贝赋值运算符
#include <iostream>
#include <string>

class MyString
{
private:
    char* data_;
    size_t size_;

public:
    MyString(const char* s = "")
    {
        size_ = strlen(s);
        data_ = new char[size_ + 1];
        strcpy(data_, s);
    }

    MyString(const MyString& other)
        : size_(other.size_)
    {
        data_ = new char[size_ + 1];
        strcpy(data_, other.data_);
    }

    // 拷贝赋值运算符
    MyString& operator=(const MyString& rhs)
    {
        std::cout << "[赋值] \"" << rhs.data_ << "\"" << std::endl;

        // 步骤1:防止自赋值(s = s)
        if (this == &rhs)
            return *this;

        // 步骤2:释放旧资源
        delete[] data_;

        // 步骤3:分配新资源并拷贝
        size_ = rhs.size_;
        data_ = new char[size_ + 1];
        strcpy(data_, rhs.data_);

        // 步骤4:返回 *this(支持链式赋值)
        return *this;
    }

    ~MyString() { delete[] data_; }

    void show() const { std::cout << data_ << std::endl; }
    const char* c_str() const { return data_; }
};

int main()
{
    using namespace std;

    MyString s1("Hello");
    MyString s2("World");
    MyString s3;

    // 赋值(不是初始化!)
    s3 = s1;           // 调用赋值运算符
    s3 = s2 = s1;      // 链式赋值:s2=s1,然后s3=s2

    // 自赋值(必须正确处理)
    s1 = s1;           // 不应崩溃

    s1.show();
    s2.show();
    s3.show();

    return 0;
}

13.1.4 析构函数

cpp 复制代码
// destructor.cpp -- 析构函数
#include <iostream>
#include <string>

class Resource
{
private:
    std::string name_;
    int*        data_;
    int         size_;

public:
    Resource(const std::string& name, int size)
        : name_(name), size_(size)
    {
        data_ = new int[size_];
        for (int i = 0; i < size_; i++) data_[i] = i;
        std::cout << "[创建] " << name_ << std::endl;
    }

    // 析构函数:释放资源
    ~Resource()
    {
        std::cout << "[销毁] " << name_ << std::endl;
        delete[] data_;   // 释放动态内存
    }

    void show() const
    {
        std::cout << name_ << ": ";
        for (int i = 0; i < size_; i++)
            std::cout << data_[i] << " ";
        std::cout << std::endl;
    }
};

// 析构函数的调用时机:
// 1. 变量离开作用域
// 2. 对象被销毁时,其成员被销毁
// 3. 容器被销毁时,其元素被销毁
// 4. 动态分配的对象被 delete 时
// 5. 临时对象在创建它的完整表达式结束时

void demoScope()
{
    Resource r("栈对象", 3);   // 构造
    r.show();
}   // r 在这里析构

int main()
{
    using namespace std;

    cout << "--- 栈对象 ---" << endl;
    demoScope();

    cout << "\n--- 堆对象 ---" << endl;
    Resource* p = new Resource("堆对象", 3);
    p->show();
    delete p;   // 手动析构

    cout << "\n--- 容器中的对象 ---" << endl;
    {
        vector<Resource> v;
        v.emplace_back("容器元素1", 2);
        v.emplace_back("容器元素2", 2);
    }   // v 离开作用域,所有元素析构

    return 0;
}

13.1.5 三/五法则

cpp 复制代码
// rule_of_three_five.cpp -- 三/五法则
#include <iostream>
#include <string>
#include <algorithm>

// 需要自定义拷贝控制的类:含有指针成员
class Buffer
{
private:
    char*  data_;
    size_t size_;

public:
    // 构造函数
    Buffer(size_t size = 0, char fill = '\0')
        : size_(size)
    {
        data_ = new char[size_ + 1];
        memset(data_, fill, size_);
        data_[size_] = '\0';
        std::cout << "[构造] size=" << size_ << std::endl;
    }

    // ===== 三法则 =====

    // 1. 拷贝构造函数(深拷贝)
    Buffer(const Buffer& other)
        : size_(other.size_)
    {
        data_ = new char[size_ + 1];
        memcpy(data_, other.data_, size_ + 1);
        std::cout << "[拷贝构造] size=" << size_ << std::endl;
    }

    // 2. 拷贝赋值运算符
    Buffer& operator=(const Buffer& rhs)
    {
        if (this == &rhs) return *this;
        delete[] data_;
        size_ = rhs.size_;
        data_ = new char[size_ + 1];
        memcpy(data_, rhs.data_, size_ + 1);
        std::cout << "[拷贝赋值] size=" << size_ << std::endl;
        return *this;
    }

    // 3. 析构函数
    ~Buffer()
    {
        std::cout << "[析构] size=" << size_ << std::endl;
        delete[] data_;
    }

    // ===== 五法则(C++11)=====

    // 4. 移动构造函数
    Buffer(Buffer&& other) noexcept
        : data_(other.data_), size_(other.size_)
    {
        other.data_ = nullptr;   // 原对象放弃所有权
        other.size_ = 0;
        std::cout << "[移动构造] size=" << size_ << std::endl;
    }

    // 5. 移动赋值运算符
    Buffer& operator=(Buffer&& rhs) noexcept
    {
        if (this == &rhs) return *this;
        delete[] data_;
        data_ = rhs.data_;
        size_ = rhs.size_;
        rhs.data_ = nullptr;
        rhs.size_ = 0;
        std::cout << "[移动赋值] size=" << size_ << std::endl;
        return *this;
    }

    size_t size() const { return size_; }
    char*  data() const { return data_; }
};

int main()
{
    using namespace std;

    cout << "===== 拷贝操作 =====" << endl;
    Buffer b1(5, 'A');
    Buffer b2 = b1;   // 拷贝构造
    Buffer b3(3, 'B');
    b3 = b1;          // 拷贝赋值

    cout << "\n===== 移动操作 =====" << endl;
    Buffer b4 = move(b1);   // 移动构造
    cout << "b1.size() = " << b1.size() << endl;   // 0(已被移走)
    cout << "b4.size() = " << b4.size() << endl;   // 5

    Buffer b5(2, 'C');
    b5 = move(b4);          // 移动赋值
    cout << "b4.size() = " << b4.size() << endl;   // 0
    cout << "b5.size() = " << b5.size() << endl;   // 5

    return 0;
}

13.2 拷贝控制和资源管理

13.2.1 行为像值的类

cpp 复制代码
// value_like_class.cpp -- 行为像值的类
#include <iostream>
#include <string>

// 行为像值:每个对象有自己独立的副本
// 拷贝时创建新的独立副本
class HasPtr
{
private:
    std::string* ps_;
    int          i_;

public:
    // 构造函数
    HasPtr(const std::string& s = std::string(), int i = 0)
        : ps_(new std::string(s)), i_(i) {}

    // 拷贝构造:深拷贝
    HasPtr(const HasPtr& p)
        : ps_(new std::string(*p.ps_)), i_(p.i_) {}

    // 拷贝赋值:深拷贝
    HasPtr& operator=(const HasPtr& rhs)
    {
        // 先创建新副本,再释放旧资源(异常安全)
        auto newp = new std::string(*rhs.ps_);
        delete ps_;
        ps_ = newp;
        i_  = rhs.i_;
        return *this;
    }

    ~HasPtr() { delete ps_; }

    void show() const
    {
        std::cout << "string=\"" << *ps_ << "\" i=" << i_ << std::endl;
    }

    void setString(const std::string& s) { *ps_ = s; }
};

int main()
{
    using namespace std;

    HasPtr h1("Hello", 1);
    HasPtr h2 = h1;   // 深拷贝

    h1.setString("Modified");   // 修改h1不影响h2

    cout << "h1: "; h1.show();
    cout << "h2: "; h2.show();   // h2 仍然是 "Hello"

    return 0;
}

13.2.2 行为像指针的类

cpp 复制代码
// pointer_like_class.cpp -- 行为像指针的类
#include <iostream>
#include <string>

// 行为像指针:多个对象共享同一份数据
// 使用引用计数管理共享资源
class HasPtr
{
private:
    std::string* ps_;
    int          i_;
    size_t*      use_;   // 引用计数

public:
    // 构造函数
    HasPtr(const std::string& s = std::string(), int i = 0)
        : ps_(new std::string(s)), i_(i), use_(new size_t(1)) {}

    // 拷贝构造:共享数据,引用计数+1
    HasPtr(const HasPtr& p)
        : ps_(p.ps_), i_(p.i_), use_(p.use_)
    {
        ++*use_;
    }

    // 拷贝赋值
    HasPtr& operator=(const HasPtr& rhs)
    {
        ++*rhs.use_;   // 先增加右侧引用计数

        // 减少左侧引用计数,如果为0则释放
        if (--*use_ == 0)
        {
            delete ps_;
            delete use_;
        }

        ps_  = rhs.ps_;
        i_   = rhs.i_;
        use_ = rhs.use_;
        return *this;
    }

    ~HasPtr()
    {
        if (--*use_ == 0)   // 引用计数为0时释放
        {
            delete ps_;
            delete use_;
        }
    }

    void show() const
    {
        std::cout << "string=\"" << *ps_
                  << "\" i=" << i_
                  << " use_count=" << *use_ << std::endl;
    }

    void setString(const std::string& s) { *ps_ = s; }
};

int main()
{
    using namespace std;

    HasPtr h1("Hello", 1);
    HasPtr h2 = h1;   // 共享数据

    cout << "h1: "; h1.show();   // use_count=2
    cout << "h2: "; h2.show();   // use_count=2

    h1.setString("Modified");   // 修改h1也影响h2(共享!)

    cout << "\n修改h1后:" << endl;
    cout << "h1: "; h1.show();
    cout << "h2: "; h2.show();   // 也变成 "Modified"

    return 0;
}

13.3 交换操作

cpp 复制代码
// swap_demo.cpp -- 交换操作
#include <iostream>
#include <string>
#include <algorithm>

class HasPtr
{
private:
    std::string* ps_;
    int          i_;

public:
    HasPtr(const std::string& s = "", int i = 0)
        : ps_(new std::string(s)), i_(i) {}

    HasPtr(const HasPtr& p)
        : ps_(new std::string(*p.ps_)), i_(p.i_) {}

    HasPtr& operator=(HasPtr rhs)   // 注意:传值!(拷贝并交换惯用法)
    {
        swap(*this, rhs);   // 交换 *this 和 rhs
        return *this;       // rhs 离开作用域时销毁旧资源
    }

    ~HasPtr() { delete ps_; }

    // 自定义 swap:交换指针,不拷贝数据(高效)
    friend void swap(HasPtr& lhs, HasPtr& rhs)
    {
        using std::swap;
        swap(lhs.ps_, rhs.ps_);   // 交换指针
        swap(lhs.i_,  rhs.i_);
        std::cout << "[swap]" << std::endl;
    }

    void show() const
    {
        std::cout << "\"" << *ps_ << "\" i=" << i_ << std::endl;
    }
};

int main()
{
    using namespace std;

    HasPtr h1("Hello", 1);
    HasPtr h2("World", 2);

    cout << "交换前:" << endl;
    cout << "h1: "; h1.show();
    cout << "h2: "; h2.show();

    swap(h1, h2);   // 调用自定义 swap

    cout << "\n交换后:" << endl;
    cout << "h1: "; h1.show();
    cout << "h2: "; h2.show();

    // 拷贝并交换惯用法(copy-and-swap idiom)
    // 赋值运算符接受值参数,自动处理自赋值和异常安全
    h1 = h2;
    cout << "\n赋值后:" << endl;
    cout << "h1: "; h1.show();

    return 0;
}

💡 拷贝并交换惯用法(copy-and-swap idiom)

  • 赋值运算符接受值参数(自动拷贝)
  • *this 交换
  • 值参数离开作用域时自动销毁旧资源
  • 优点:自动处理自赋值、异常安全、代码简洁

13.4 移动操作

13.4.1 右值引用

cpp 复制代码
// rvalue_reference.cpp -- 右值引用
#include <iostream>
#include <string>
#include <utility>

int main()
{
    using namespace std;

    // 左值:有名字,有持久状态,可以取地址
    int x = 42;
    string s = "Hello";

    // 右值:临时值,没有持久状态
    // 42, "Hello", x+1, string("temp") 都是右值

    // 左值引用:只能绑定左值
    int& lref = x;   // ✅
    // int& lref2 = 42;   // ❌

    // const 左值引用:可以绑定右值
    const int& clref = 42;   // ✅

    // 右值引用(&&):只能绑定右值
    int&& rref = 42;         // ✅
    int&& rref2 = x + 1;    // ✅(表达式结果是右值)
    // int&& rref3 = x;     // ❌ x 是左值

    // std::move:将左值转为右值引用
    int&& rref4 = move(x);   // ✅ 显式移动
    // 注意:move 之后,x 的值是未定义的(不要再使用)

    cout << "rref = " << rref << endl;
    cout << "rref2 = " << rref2 << endl;

    // 右值引用的用途:移动语义
    string s1 = "Hello";
    string s2 = move(s1);   // 移动构造,s1 变为空
    cout << "s1 = \"" << s1 << "\"" << endl;   // 空
    cout << "s2 = \"" << s2 << "\"" << endl;   // "Hello"

    return 0;
}

13.4.2 移动构造函数和移动赋值运算符

cpp 复制代码
// move_operations.cpp -- 移动操作
#include <iostream>
#include <string>
#include <utility>

class StrVec
{
private:
    std::string* elements_;   // 指向数组首元素
    std::string* first_free_; // 指向第一个空闲位置
    std::string* cap_;        // 指向数组末尾的下一位置

    static std::allocator<std::string> alloc;

    void free()
    {
        if (elements_)
        {
            for (auto p = first_free_; p != elements_; )
                alloc.destroy(--p);
            alloc.deallocate(elements_, cap_ - elements_);
        }
    }

    void reallocate()
    {
        size_t newcap = size() ? 2 * size() : 1;
        auto newdata = alloc.allocate(newcap);
        auto dest = newdata;
        auto elem = elements_;
        for (size_t i = 0; i != size(); ++i)
            alloc.construct(dest++, std::move(*elem++));
        free();
        elements_   = newdata;
        first_free_ = dest;
        cap_        = elements_ + newcap;
    }

public:
    StrVec() : elements_(nullptr), first_free_(nullptr), cap_(nullptr) {}

    // 拷贝构造
    StrVec(const StrVec& s)
    {
        auto data = alloc.allocate(s.size());
        auto dest = data;
        for (const auto& str : s)
            alloc.construct(dest++, str);
        elements_   = data;
        first_free_ = dest;
        cap_        = data + s.size();
        std::cout << "[拷贝构造] size=" << size() << std::endl;
    }

    // 移动构造函数:noexcept 很重要!
    StrVec(StrVec&& s) noexcept
        : elements_(s.elements_),
          first_free_(s.first_free_),
          cap_(s.cap_)
    {
        // 将原对象置于可析构状态
        s.elements_ = s.first_free_ = s.cap_ = nullptr;
        std::cout << "[移动构造] size=" << size() << std::endl;
    }

    // 拷贝赋值
    StrVec& operator=(const StrVec& rhs)
    {
        auto data = alloc.allocate(rhs.size());
        auto dest = data;
        for (const auto& str : rhs)
            alloc.construct(dest++, str);
        free();
        elements_   = data;
        first_free_ = dest;
        cap_        = data + rhs.size();
        std::cout << "[拷贝赋值] size=" << size() << std::endl;
        return *this;
    }

    // 移动赋值运算符
    StrVec& operator=(StrVec&& rhs) noexcept
    {
        if (this != &rhs)
        {
            free();
            elements_   = rhs.elements_;
            first_free_ = rhs.first_free_;
            cap_        = rhs.cap_;
            rhs.elements_ = rhs.first_free_ = rhs.cap_ = nullptr;
        }
        std::cout << "[移动赋值] size=" << size() << std::endl;
        return *this;
    }

    ~StrVec() { free(); }

    void push_back(const std::string& s)
    {
        if (first_free_ == cap_) reallocate();
        alloc.construct(first_free_++, s);
    }

    size_t size()     const { return first_free_ - elements_; }
    size_t capacity() const { return cap_ - elements_; }

    std::string* begin() const { return elements_; }
    std::string* end()   const { return first_free_; }
};

std::allocator<std::string> StrVec::alloc;

int main()
{
    using namespace std;

    StrVec sv1;
    sv1.push_back("Hello");
    sv1.push_back("World");
    sv1.push_back("C++");

    cout << "===== 拷贝 =====" << endl;
    StrVec sv2 = sv1;   // 拷贝构造

    cout << "\n===== 移动 =====" << endl;
    StrVec sv3 = move(sv1);   // 移动构造
    cout << "sv1.size() = " << sv1.size() << endl;   // 0
    cout << "sv3.size() = " << sv3.size() << endl;   // 3

    cout << "\n===== 移动赋值 =====" << endl;
    StrVec sv4;
    sv4 = move(sv3);   // 移动赋值

    return 0;
}

13.4.3 合成的移动操作

cpp 复制代码
// synthesized_move.cpp -- 合成的移动操作
#include <iostream>
#include <string>

// 编译器合成移动操作的条件:
// 1. 没有用户定义的拷贝操作(拷贝构造/拷贝赋值/析构)
// 2. 所有非静态数据成员都可以移动

struct X
{
    int    i;      // 内置类型,可以移动
    string s;      // string 有移动操作
    // 编译器会合成移动构造和移动赋值
};

struct HasY
{
    HasY() = default;
    HasY(HasY&&) = default;   // 显式要求合成移动构造
    Y y;
};

// 定义了析构函数,移动操作不会被合成
struct NoMove
{
    NoMove() = default;
    ~NoMove() {}   // 定义了析构函数
    // 移动操作不会被合成,但拷贝操作会被合成
};

// 显式要求合成
struct WithMove
{
    WithMove() = default;
    WithMove(const WithMove&) = default;
    WithMove& operator=(const WithMove&) = default;
    ~WithMove() = default;

    // 显式要求合成移动操作
    WithMove(WithMove&&) = default;
    WithMove& operator=(WithMove&&) = default;
};

int main()
{
    using namespace std;

    X x1;
    x1.i = 42;
    x1.s = "Hello";

    X x2 = move(x1);   // 使用合成的移动构造
    cout << "x1.s = \"" << x1.s << "\"" << endl;   // 空(已被移走)
    cout << "x2.s = \"" << x2.s << "\"" << endl;   // "Hello"

    return 0;
}

13.5 右值引用和成员函数

cpp 复制代码
// rvalue_member.cpp -- 右值引用和成员函数
#include <iostream>
#include <string>
#include <vector>

class StrVec
{
public:
    // 重载 push_back:接受左值和右值
    void push_back(const std::string& s)   // 拷贝版本
    {
        std::cout << "[拷贝push_back] " << s << std::endl;
        data_.push_back(s);
    }

    void push_back(std::string&& s)   // 移动版本
    {
        std::cout << "[移动push_back] " << s << std::endl;
        data_.push_back(std::move(s));
    }

    // 引用限定符:限制成员函数只能被左值或右值调用
    StrVec& sorted() &    // 只能被左值调用
    {
        std::sort(data_.begin(), data_.end());
        return *this;
    }

    StrVec sorted() &&    // 只能被右值调用
    {
        std::sort(data_.begin(), data_.end());
        return std::move(*this);
    }

    void show() const
    {
        for (const auto& s : data_)
            std::cout << s << " ";
        std::cout << std::endl;
    }

private:
    std::vector<std::string> data_;
};

int main()
{
    using namespace std;

    StrVec sv;

    string s1 = "Hello";
    sv.push_back(s1);           // 拷贝版本(s1是左值)
    sv.push_back("World");      // 移动版本(字面量是右值)
    sv.push_back(move(s1));     // 移动版本(显式移动)

    sv.show();

    // 引用限定符
    sv.sorted();   // 调用左值版本(sv是左值)

    // StrVec().sorted();   // 调用右值版本(临时对象是右值)

    return 0;
}

13.6 综合示例:完整的 String 类

cpp 复制代码
// complete_string.cpp -- 综合示例:完整的String类
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stdexcept>
#include <utility>

class String
{
private:
    char*  data_;
    size_t size_;
    size_t capacity_;

    static size_t nextCapacity(size_t needed)
    {
        size_t cap = 1;
        while (cap <= needed) cap *= 2;
        return cap;
    }

    void reallocate(size_t newCap)
    {
        char* newData = new char[newCap + 1];
        if (data_)
        {
            memcpy(newData, data_, size_ + 1);
            delete[] data_;
        }
        data_     = newData;
        capacity_ = newCap;
    }

public:
    // ===== 构造函数 =====
    String() : data_(nullptr), size_(0), capacity_(0)
    {
        data_    = new char[1];
        data_[0] = '\0';
    }

    String(const char* s)
    {
        size_     = strlen(s);
        capacity_ = nextCapacity(size_);
        data_     = new char[capacity_ + 1];
        memcpy(data_, s, size_ + 1);
    }

    String(size_t n, char c)
    {
        size_     = n;
        capacity_ = nextCapacity(n);
        data_     = new char[capacity_ + 1];
        memset(data_, c, n);
        data_[n]  = '\0';
    }

    // ===== 拷贝构造函数 =====
    String(const String& other)
        : size_(other.size_), capacity_(other.capacity_)
    {
        data_ = new char[capacity_ + 1];
        memcpy(data_, other.data_, size_ + 1);
        std::cout << "[拷贝构造]" << std::endl;
    }

    // ===== 移动构造函数 =====
    String(String&& other) noexcept
        : data_(other.data_), size_(other.size_), capacity_(other.capacity_)
    {
        other.data_     = nullptr;
        other.size_     = 0;
        other.capacity_ = 0;
        std::cout << "[移动构造]" << std::endl;
    }

    // ===== 拷贝赋值运算符(拷贝并交换)=====
    String& operator=(String rhs)   // 传值:自动拷贝或移动
    {
        swap(*this, rhs);
        return *this;
    }

    // ===== 析构函数 =====
    ~String() { delete[] data_; }

    // ===== swap =====
    friend void swap(String& a, String& b) noexcept
    {
        using std::swap;
        swap(a.data_,     b.data_);
        swap(a.size_,     b.size_);
        swap(a.capacity_, b.capacity_);
    }

    // ===== 访问 =====
    char&       operator[](size_t i)       { return data_[i]; }
    const char& operator[](size_t i) const { return data_[i]; }

    char& at(size_t i)
    {
        if (i >= size_) throw std::out_of_range("String::at");
        return data_[i];
    }

    size_t size()     const { return size_; }
    size_t capacity() const { return capacity_; }
    bool   empty()    const { return size_ == 0; }
    const char* c_str() const { return data_; }

    // ===== 修改 =====
    String& append(const char* s)
    {
        size_t slen = strlen(s);
        if (size_ + slen > capacity_)
            reallocate(nextCapacity(size_ + slen));
        memcpy(data_ + size_, s, slen + 1);
        size_ += slen;
        return *this;
    }

    String& operator+=(const String& rhs) { return append(rhs.data_); }
    String& operator+=(const char* s)     { return append(s); }

    // ===== 比较 =====
    friend bool operator==(const String& a, const String& b)
    { return strcmp(a.data_, b.data_) == 0; }

    friend bool operator<(const String& a, const String& b)
    { return strcmp(a.data_, b.data_) < 0; }

    friend bool operator!=(const String& a, const String& b)
    { return !(a == b); }

    // ===== 输出 =====
    friend std::ostream& operator<<(std::ostream& os, const String& s)
    {
        os << s.data_;
        return os;
    }
};

// 拼接运算符
String operator+(String lhs, const String& rhs)
{
    lhs += rhs;
    return lhs;   // 移动返回
}

int main()
{
    using namespace std;

    String s1("Hello");
    String s2(", World");
    String s3 = s1;          // 拷贝构造
    String s4 = move(s1);    // 移动构造

    cout << "s3 = " << s3 << endl;
    cout << "s4 = " << s4 << endl;
    cout << "s1 = \"" << s1 << "\"(已被移走)" << endl;

    // 拼接
    String s5 = s3 + s2;
    cout << "s5 = " << s5 << endl;

    // 赋值(拷贝并交换)
    String s6;
    s6 = s5;   // 拷贝赋值
    cout << "s6 = " << s6 << endl;

    s6 = move(s5);   // 移动赋值
    cout << "s6 = " << s6 << endl;

    // 比较
    cout << boolalpha;
    cout << "s3 == s4: " << (s3 == s4) << endl;
    cout << "s3 < s2:  " << (s3 < s2)  << endl;

    return 0;
}

📝 第13章知识点总结

知识点 核心要点
三法则 需要析构函数时,通常也需要拷贝构造和拷贝赋值
五法则 C++11,定义移动操作时,通常需要定义所有五个特殊成员
拷贝构造函数 参数必须是 const T&(否则无限递归),深拷贝含指针的类
拷贝赋值运算符 防自赋值→释放旧资源→深拷贝→返回*this
析构函数 释放资源,不能抛出异常,调用时机:离开作用域/delete/容器销毁
行为像值 每个对象有独立副本,拷贝时深拷贝
行为像指针 多个对象共享数据,用引用计数管理
拷贝并交换 赋值运算符接受值参数,与*this交换,自动处理自赋值和异常安全
右值引用 && 只绑定右值,是移动语义的基础
移动构造函数 T(T&&) noexcept,转移资源所有权,原对象置于有效但未定义状态
移动赋值运算符 T& operator=(T&&) noexcept,释放自身→接管对方→对方置空
noexcept 移动操作应标记 noexcept,否则容器可能不使用移动操作
合成移动操作 没有用户定义拷贝操作且所有成员可移动时,编译器合成
引用限定符 &(只能被左值调用)和 &&(只能被右值调用)
= default 显式要求编译器合成默认实现
= delete 阻止编译器合成,或禁止特定操作
相关推荐
z落落1 小时前
C# 静态成员 vs 非静态成员(调用规则+内存特点)+只读和常量 const常量 / readonly / static readonly 三者终极区别
java·开发语言·c#
zhangfeng11331 小时前
超算中心 高性能计算 slurm的linux版本 centos7,如何安装docker,如何安装torch2.4
linux·运维·服务器·开发语言·人工智能·机器学习·docker
c238561 小时前
map和set
数据结构·c++
java1234_小锋1 小时前
LangChain4j 开发Java Agent智能体- 整合SpringBoot4
java·开发语言·langchain4j
basketball6161 小时前
C++进阶:3. unique_ptr 现代C++内存管理的基石
java·jvm·c++
FFZero11 小时前
[mpv脚本系统] (三) C 函数如何注册成 Lua 模块
c++·音视频·lua
我不是懒洋洋1 小时前
从零实现一个Redis客户端:RESP协议与网络编程
开发语言·c++
玖玥拾1 小时前
C/C++ 基础笔记(六)
c语言·c++·内存管理
小小码农Come on2 小时前
Qt::WA_StyledBackground属性的作用
开发语言·qt