文章目录
- 概述
- 13.1拷贝、赋值与销毁
-
- 合成拷贝构造函数
- 拷贝初始化
- 参数和返回值
- 拷贝初始化的限制
- 编译器可以绕过拷贝构造函数
- 拷贝运算符
- 析构函数
- 三/五原则
- private拷贝控制
- 拷贝控制和资源管理
-
- 行为像值的类
- 类值拷贝赋值运算符
- 定义行为像指针的类
- 引用计数
- 定义一个引用计数的类
- [类指针的拷贝成员"篡改" 引用计数](#类指针的拷贝成员“篡改” 引用计数)
- 交换操作
- 拷贝控制示例
- 动态内存管理
- 对象移动
概述

13.1拷贝、赋值与销毁

合成拷贝构造函数
拷贝初始化
参数和返回值

拷贝初始化的限制

编译器可以绕过拷贝构造函数

拷贝运算符
析构函数
三/五原则
使用=default

阻止拷贝
### 析构函数不能是删除成员

合成的拷贝控制成员可能是删除的
private拷贝控制
在新标准发布之前 ,类是通过将其拷贝构造函数和拷贝赋值运算符芦明为 pr ivate
的来阻止拷贝:
class Priva teCopy {
//元访问说明符 ;接下来的成员默认为p rivate 的;参见 7.2 节 ( 第 240 页 )
//拷贝控制成员是 priv a te 的,因此普通用户代码无法访问
Priva teCopy ( const Priva teCopy& ) ;
PrivateCopy & operator= ( const Priva teCopy& ) ;
//其他成员
public :
Pr ivateCopy ( ) = def au lt ; //使用合成的默认构造函数
~PrivateCopy ( ) ; // 用尸可以定义此类型的对象 ,但无法拷贝它们
由于析构函数是 public 的,用户可以定义 Pr ivateCopy 类型的对象 。但是 ,由于拷贝 构造函数和拷贝赋值运算符是 pr iva te 的,用户代码将不能拷贝这个类型的对象
。但是, 友元和成员函数仍旧可以拷贝对象 。为了阻止友元和成员函数进行拷贝,我们将这些拷贝 控制成员声明为 pr iva t e 的,但并不定义它们 。
声明但不定义 个成员函数是合法的 ( 参见 6. 1.2 节,第 186 页),对此只有一个例外 , 我们将在 15.2.1 节 (第 528 页) 中介绍。试图访问一个未定义的成员将导致
一个链接时错 误。通过声明 (但不定义) pr ivate 的拷贝构造函数 ,我们可以预先阻止任何拷贝该类 型对象的企图 :试图拷贝对象的用户代码将在编译阶段被标记为错 误:成员函数或友元函
数中的拷贝操作将会导致链接时错误 。
拷贝控制和资源管理

行为像值的类
类值拷贝赋值运算符
定义行为像指针的类

引用计数

定义一个引用计数的类
类指针的拷贝成员"篡改" 引用计数
交换操作
拷贝控制示例
动态内存管理
对象移动
右值引用
移动构造函数和移动赋值运算符
csharp
#pragma once
#include <iostream>
class HasPtr
{
friend void swap(HasPtr&, HasPtr&);
public:
//默认构造函数
HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0)
{
}
//拷贝构造函数
HasPtr(const HasPtr& data) : ps(new std::string(*(data.ps))), i(data.i)
{
}
//添加都移动构造函数
HasPtr(HasPtr&& P)noexcept :ps(P.ps), i(P.i) {
P.ps = nullptr;
P.i = 0;
}
HasPtr& operator=(HasPtr rhs)
{
swap(*this, rhs);
return *this;
}
// 析构函数
~HasPtr()
{
delete ps;
}
private:
std::string* ps;
int i;
};
inline void swap(HasPtr& lhs, HasPtr& rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
swap(lhs.i, rhs.i); // swap the int members
}
int main()
{
HasPtr data1, data2("123"), data3, data4("abc");
data1 = data2;//拷贝语义
data3 = std::move(data4);//移动语义
}
所有五个拷贝控制成员应该看作一个整体:一般来说,如果一个类定义了任何一个拷贝操作,它应该定义所有五个操作。如前所述,某些类必须定义拷贝构造函数、拷贝赋值运算符和析构函数。这些类只有一个资源,而拷贝成员必须拷贝此资源。
移动迭代器
csharp
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <algorithm>
#include <cassert>
class StrVec {
public:
// 默认构造函数
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {
std::cout << "Default constructor called." << std::endl;
}
// 拷贝构造函数
StrVec(const StrVec& s) {
std::cout << "Copy constructor called." << std::endl;
auto newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
// 移动构造函数
StrVec(StrVec&& s) noexcept : elements(s.elements), first_free(s.first_free), cap(s.cap) {
std::cout << "Move constructor called." << std::endl;
s.elements = s.first_free = s.cap = nullptr;
}
// 拷贝赋值运算符
StrVec& operator=(const StrVec& rhs) {
std::cout << "Copy assignment operator called." << std::endl;
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
// 移动赋值运算符
StrVec& operator=(StrVec&& rhs) noexcept {
std::cout << "Move assignment operator called." << std::endl;
if (this != &rhs) {
free();
elements = rhs.elements;
first_free = rhs.first_free;
cap = rhs.cap;
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
// 析构函数
~StrVec() {
std::cout << "Destructor called." << std::endl;
free();
}
// 拷贝版本的 push_back
void push_back(const std::string& s) {
std::cout << "Copy push_back called with: " << s << std::endl;
chk_n_alloc();
alloc.construct(first_free++, s);
}
// 移动版本的 push_back
void push_back(std::string&& s) {
std::cout << "Move push_back called with: " << s << std::endl;
chk_n_alloc();
alloc.construct(first_free++, std::move(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; }
// 左值版本的 sorted 方法
void sorted() & {
std::cout << "Lvalue sorted called." << std::endl;
std::sort(begin(), end());
}
// 右值版本的 sorted 方法
StrVec sorted() && {
std::cout << "Rvalue sorted called." << std::endl;
std::sort(begin(), end());
return std::move(*this);
}
private:
std::allocator<std::string> alloc;
// 检查是否需要重新分配内存
void chk_n_alloc() {
if (size() == capacity()) {
reallocate();
}
}
// 分配并复制元素
std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
// 释放内存
void free();
// 重新分配内存
void reallocate();
std::string* elements;
std::string* first_free;
std::string* cap;
};
// 分配并复制元素的实现
std::pair<std::string*, std::string*> StrVec::alloc_n_copy(const std::string* b, const std::string* e) {
auto data = alloc.allocate(e - b);
return {data, std::uninitialized_copy(b, e, data)};
}
// 释放内存的实现
void StrVec::free() {
if (elements) {
for (auto p = first_free; p != elements; ) {
alloc.destroy(--p);
}
alloc.deallocate(elements, cap - elements);
}
}
// 重新分配内存的实现
void StrVec::reallocate() {
std::cout << "Reallocating memory..." << std::endl;
auto newcapacity = size() ? 2 * size() : 1;
auto newdata = alloc.allocate(newcapacity);
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 + newcapacity;
}
// 打印 StrVec 中的元素
void printStrVec(const StrVec& vec) {
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
// 测试默认构造函数
void testDefaultConstructor() {
StrVec vec;
assert(vec.size() == 0);
std::cout << "Default constructor test passed." << std::endl;
}
// 测试拷贝构造函数
void testCopyConstructor() {
StrVec vec1;
vec1.push_back("apple");
vec1.push_back("banana");
StrVec vec2(vec1);
assert(vec2.size() == vec1.size());
auto it1 = vec1.begin();
auto it2 = vec2.begin();
while (it1 != vec1.end() && it2 != vec2.end()) {
assert(*it1 == *it2);
++it1;
++it2;
}
std::cout << "Copy constructor test passed." << std::endl;
}
// 测试移动构造函数
void testMoveConstructor() {
StrVec vec1;
vec1.push_back("cherry");
vec1.push_back("date");
StrVec vec2(std::move(vec1));
assert(vec1.size() == 0);
assert(vec2.size() == 2);
std::cout << "Move constructor test passed." << std::endl;
}
// 测试拷贝赋值运算符
void testCopyAssignmentOperator() {
StrVec vec1;
vec1.push_back("elderberry");
vec1.push_back("fig");
StrVec vec2;
vec2 = vec1;
assert(vec2.size() == vec1.size());
auto it1 = vec1.begin();
auto it2 = vec2.begin();
while (it1 != vec1.end() && it2 != vec2.end()) {
assert(*it1 == *it2);
++it1;
++it2;
}
std::cout << "Copy assignment operator test passed." << std::endl;
}
// 测试移动赋值运算符
void testMoveAssignmentOperator() {
StrVec vec1;
vec1.push_back("grape");
vec1.push_back("honeydew");
StrVec vec2;
vec2 = std::move(vec1);
assert(vec1.size() == 0);
assert(vec2.size() == 2);
std::cout << "Move assignment operator test passed." << std::endl;
}
// 测试 push_back 方法
void testPushBack() {
StrVec vec;
vec.push_back("apple");
vec.push_back(std::string("banana"));
assert(vec.size() == 2);
std::cout << "Push_back test passed." << std::endl;
}
// 测试 sorted 方法(左值)
void testSortedLvalue() {
StrVec vec;
vec.push_back("banana");
vec.push_back("apple");
vec.push_back("cherry");
vec.sorted();
auto it = vec.begin();
assert(*it == "apple");
++it;
assert(*it == "banana");
++it;
assert(*it == "cherry");
std::cout << "Sorted (lvalue) test passed." << std::endl;
}
// 测试 sorted 方法(右值)
void testSortedRvalue() {
StrVec sortedVec = StrVec().push_back("grape").push_back("date").push_back("elderberry").sorted();
auto it = sortedVec.begin();
assert(*it == "date");
++it;
assert(*it == "elderberry");
++it;
assert(*it == "grape");
std::cout << "Sorted (rvalue) test passed." << std::endl;
}
int main() {
testDefaultConstructor();
testCopyConstructor();
testMoveConstructor();
testCopyAssignmentOperator();
testMoveAssignmentOperator();
testPushBack();
testSortedLvalue();
testSortedRvalue();
std::cout << "All tests passed!" << std::endl;
return 0;
}
}