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;
}    
}    
相关推荐
likuolei7 分钟前
XQuery 完整语法速查表(2025 最新版,XQuery 3.1)
xml·java·数据库
雨中飘荡的记忆13 分钟前
LangChain4j 实战指南
java·langchain
okseekw15 分钟前
Java 中的方法:从定义到重载的完整指南
java
雨中飘荡的记忆15 分钟前
深入理解设计模式之适配器模式
java·设计模式
用户849137175471616 分钟前
生产级故障排查实战:从制造 OOM 到 IDEA Profiler 深度破案
java·jvm
雨中飘荡的记忆20 分钟前
深入理解设计模式之装饰者模式
java·设计模式
雨中飘荡的记忆24 分钟前
秒杀系统设计与实现
java·redis·lua
CryptoPP29 分钟前
使用 KLineChart 这个轻量级的前端图表库
服务器·开发语言·前端·windows·后端·golang
18你磊哥35 分钟前
chromedriver.exe的使用和python基本处理
开发语言·python
小坏讲微服务1 小时前
Spring Cloud Alibaba 整合 Scala 教程完整使用
java·开发语言·分布式·spring cloud·sentinel·scala·后端开发