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 | 阻止编译器合成,或禁止特定操作 |