C++ Primer (第五版)-第十三章 拷贝控制

文章目录

概述

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;
}    
}    
相关推荐
Dovis(誓平步青云)2 分钟前
探索C++标准模板库(STL):String接口的底层实现(下篇)
开发语言·c++·stl·string
海棠一号7 分钟前
JAVA理论第五章-JVM
java·开发语言·jvm
eternal__day23 分钟前
Spring Cloud 多机部署与负载均衡实战详解
java·spring boot·后端·spring cloud·负载均衡
颜淡慕潇27 分钟前
Redis 实现分布式锁:深入剖析与最佳实践(含Java实现)
java·redis·分布式
程序员秘密基地33 分钟前
基于vscode,idea,java,html,css,vue,echart,maven,springboot,mysql数据库,在线考试系统
java·vue.js·spring boot·spring·web app
何中应35 分钟前
【设计模式-5】设计模式的总结
java·后端·设计模式
草莓熊Lotso35 分钟前
【数据结构初阶】--算法复杂度的深度解析
c语言·开发语言·数据结构·经验分享·笔记·其他·算法
KyollBM42 分钟前
【CF】Day75——CF (Div. 2) B (数学 + 贪心) + CF 882 (Div. 2) C (01Trie | 区间最大异或和)
c语言·c++·算法
海的诗篇_1 小时前
前端开发面试题总结-JavaScript篇(二)
开发语言·前端·javascript·typescript
吾日三省吾码1 小时前
Spring 团队详解:AOT 缓存实践、JSpecify 空指针安全与支持策略升级
java·spring·缓存