当前位置: 首页> 文旅> 文化 > 河北省建设工程招标网_申请163 com免费邮箱_优化大师安卓版_百度搜索指数排名

河北省建设工程招标网_申请163 com免费邮箱_优化大师安卓版_百度搜索指数排名

时间:2025/7/12 7:00:04来源:https://blog.csdn.net/gaopeng1111/article/details/142733161 浏览次数:0次
河北省建设工程招标网_申请163 com免费邮箱_优化大师安卓版_百度搜索指数排名

        系统中的动态资源、文件句柄(socket描述符、文件描述符)是有限的,在类中若涉及对此类资源的操作,但是未做到妥善的管理,常会造成资源泄露问题,严重的可能造成资源不可用,如申请内存失败、文件句柄申请失败;或引发未定义行为,进而引起程序崩溃、表现出意外的行为、损坏数据,或者可能看似正常工作但在其它情况下出现问题。

        三之法则和五之法则可以很好地解决上述问题。它们帮助开发者理解和管理类的拷贝控制操作,避免常见的资源泄露、重复释放等问题,并优化程序的性能。零之法则关注类的特殊成员函数的声明和使用。在实际开发中,应根据类的具体需求来决定是否需要自定义这些特殊的成员函数。

学在前面

1、深拷贝和浅拷贝

        深拷贝和浅拷贝是两种不同的对象复制方式,它们涉及到对象的内存管理和数据成员的处理方式。若类拥有资源类(指针、文件句柄)的成员,类的对象间进行复制时,若资源重新进行分配,为深拷贝;否则为浅拷贝。以下以类中包含指针数据成员为例进行描述。

1.1 概念

        深拷贝:复制后新对象和旧对象的指针成员占用不同的内存空间。

        浅拷贝:复制后新对象和旧对象的指针成员占用相同的内存空间。

1.2 特征对比

        深拷贝

  • 复制对象的所有成员值。
  • 对于指针类型的成员,分配新的内存区域,并复制指针指向的实际数据。
  • 修改源对象或新对象的指针指向的数据,不会影响另一个对象。

        浅拷贝

  • 复制对象的所有成员值。
  • 对于指针类型的成员,仅复制指针值,而不复制指针指向的实际数据。
  • 如果源对象或新对象在生命周期内修改了指针指向的数据,则另一个对象也会受到影响。

1.3 示例

1.3.1  浅拷贝
#include <iostream>
#include <cstring>class ShallowCopy {
public:explicit ShallowCopy(const char *str){data_ = new char[strlen(str) + 1];strcpy(data_, str);}ShallowCopy(const ShallowCopy &other){data_ = other.data_;}~ShallowCopy(){delete[] data_;}void MemberAddress(const std::string &item) const{if (data_ == nullptr) {std::cout << "data_ is NULL" << std::endl;}std::cout << item << &data_ << std::endl;}
private:char *data_;
};int main()
{ShallowCopy obj1("ShallowCopy");ShallowCopy obj2 = obj1;obj1.MemberAddress("old obj address ");obj2.MemberAddress("new obj address ");return 0;
}

思考:

1、打印的地址预期一致,实际一致吗?

一致。

因为两个对象的指针成员在复制时,只是进行指针地址的复制,指向同一块内存。

2、代码会执行成功吗?

不会。

在obj1、obj2的作用域结束后,会执行析构函数,首先释放obj1的成员data_的内存,再释放obj2的成员data_的内存,由于两个内存指向同一地址,所以同一内存会释放两次,引发core dump,异常退出。

old obj address 0x7fff3e60bab0
new obj address 0x7fff3e60bab8
free(): double free detected in tcache 2
Aborted (core dumped)

3、 ShallowCopy obj2 = obj1;替换为如下两行可以吗?

ShallowCopy obj2;
obj2 = obj1;

 按照目前实现是不可以的,因为已自定义构造函数,默认的构造函数不会生成,obj2无对应的构造函数。

1.3.2  深拷贝
#include <iostream>
#include <cstring>
#include <string>class DeepCopy {
public:explicit DeepCopy(const char *str){data_ = new char[strlen(str) + 1];strcpy(data_, str);}DeepCopy(const DeepCopy &other){data_ = new char[strlen(other.data_) + 1];strcpy(data_, other.data_);}DeepCopy& operator=(const DeepCopy &other){if (this == &other) {return *this; // handle self-assignment}delete[] data_;data_ = new char[strlen(other.data_) + 1];strcpy(data_, other.data_);return *this;}~DeepCopy(){delete[] data_;}void MemberAddress(const std::string &item) const{if (data_ == nullptr) {std::cout << "data_ is NULL" << std::endl;} else {std::cout << item << ": " << &data_ << std::endl;}}private:char *data_;
};int main()
{DeepCopy copy1("Hello, World!");DeepCopy copy2 = copy1;  // Use the copy constructorcopy1.MemberAddress("old obj address of data_ ");copy2.MemberAddress("new obj address of data_ ");return 0;
}

执行结果:

old obj address of data_ : 0x7fff918a0970
new obj address of data_ : 0x7fff918a0978

1.4 图示深浅拷贝差异

浅拷贝

深拷贝

2、RAII

2.1  概念

RAII是Resource Acquisition Is Initialization的缩写,它是一种管理资源的技术,其核心思想是将资源的获取与对象的初始化绑定在一起,并通过对象的生命周期来自动管理资源的释放。

2.2  特征

资源获取即初始化:当对象被创建时,自动获取所需的资源,通常在构造函数中完成。

析构函数管理资源释放:当对象被销毁时,析构函数用于释放资源。

异常安全性:RAII机制确保即使在发生异常的情况下,资源也能被正确释放。

2.3 示例

2.3.1 文件句柄类
class FileHandle {
public:FileHandle(const std::string &filename, std::ios_base::openmode mode) {file_.open(filename, mode);if (!file_.is_open()) {throw std::runtime_error("Failed to open file: " + filename);}}~FileHandle() {if (file_.is_open()) {file_.close();}}private:std::fstream file_; 
};
2.3.2 RAII妙用——时间戳打点 
class TimestampLogger {
public:TimestampLogger(const std::string &description): description_(description), start_(std::chrono::steady_clock::now()) {}~TimestampLogger() {auto end = std::chrono::steady_clock::now();std::chrono::duration<double> elapsed = end - start_;std::cout << description_ << " took " << elapsed.count() << " seconds.\n";}// 禁用拷贝构造函数和赋值运算符,防止资源泄露或重复打点TimestampLogger(const TimestampLogger &) = delete;TimestampLogger &operator=(const TimestampLogger &) = delete;private:std::string description_;std::chrono::steady_clock::time_point start_;
};

一、三之法则

1、概念

        三之法则,也称为“三大定律”或“三法则”,它指出,如果类定义了以下三个特殊成员函数之一:析构函数、拷贝构造函数或拷贝赋值运算符,则开发者通常也需要定义其它两个特殊成员函数,以确保类的拷贝控制和资源管理行为的正确性。

2、使用场景

        三之法则主要是为了避免资源泄露、重复释放或其它由于浅拷贝导致的错误。

        默认情况下,编译器会为类生成默认的析构函数、拷贝构造函数和拷贝赋值运算符,但这些默认实现通常只进行浅拷贝,即只复制对象的成员变量的值,而不复制成员变量所指向的资源。如果成员变量是指针,并且指向动态分配的内存,则浅拷贝会导致两个对象共享同一块内存,从而在销毁时发生重复释放的错误。

3、如何实现

定义所有需要的特殊成员函数:如果类需要自定义其中一个特殊成员函数,那么通常也需要自定义其他两个成员函数,以确保对象的拷贝和赋值行为符合预期。

理解资源管理:了解类所管理的资源,并决定是否需要自定义特殊成员函数来管理这些资源的拷贝和赋值。

使用RAII:将资源的生命周期与对象的生命周期绑定,简化资源管理,降低资源泄露风险。

4、示例

见1.3.1

二、五之法则

1、概念

        五之法则在C++11及以后版本引入,它在三之法则的基础上增加了两个新的特殊成员函数:移动构造函数和移动赋值运算符,以支持移动语义。

2、使用场景

        五之法则的引入是为了进一步提高程序的性能,特别是在处理大型对象或资源密集型对象时。通过允许对象之间的资源移动而不是复制,可以减少不必要的内存分配和释放操作,从而提高程序的运行效率。

3、如何实现

定义所有五个特殊成员函数:如果类需要移动语义,则应该定义所有五个特殊成员函数(析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符)。

使用noexcept关键字:在C++11及以后版本中,移动构造函数和移动赋值运算符通常会被标记为noexcept,表明它们不会抛出异常。这有助于编译器优化代码,并允许在更多情况下使用移动语义。

理解移动语义:了解移动语义的工作原理,并决定何时以及如何使用它来优化程序的性能。

4、示例

4.1 socket描述符

class SocketDescriptor {
public:SocketDescriptor() : fd(-1) {}explicit SocketDescriptor(int socket_fd) : fd(socket_fd){if (fd == -1) {throw std::runtime_error("Invalid socket descriptor");}}~SocketDescriptor(){closeSocket();}SocketDescriptor(const SocketDescriptor &) = delete;SocketDescriptor &operator=(const SocketDescriptor&) = delete; SocketDescriptor(SocketDescriptor &&other) noexcept : fd(other.fd){other.fd = -1;}SocketDescriptor &operator=(SocketDescriptor&& other) noexcept{if (this != &other) {closeSocket();fd = other.fd;other.fd = -1;}return *this;}private:void closeSocket(){if (fd != -1) {::close(fd);fd = -1;}}private:int fd;
};

4.2 拷贝"大"数据

class LargeData {
public:LargeData() = default;explicit LargeData(size_t dataSize){try {data = new char[dataSize];this->dataSize = dataSize;std::fill_n(data, dataSize, 0);} catch (const std::bad_alloc&) {throw std::runtime_error("Memory allocation failed in LargeData constructor");}}~LargeData(){delete[] data;}LargeData(const LargeData& other) : dataSize(other.dataSize){try {data = new char[dataSize];std::copy(other.data, other.data + dataSize, data);} catch (const std::bad_alloc&) {throw std::runtime_error("Memory allocation failed in LargeData copy constructor");}}LargeData& operator=(const LargeData& other){if (this == &other) {return *this;}char* oldData = data;try {dataSize = other.dataSize;data = new char[dataSize];std::copy(other.data, other.data + dataSize, data);} catch (const std::bad_alloc&) {dataSize = 0;data = oldData;throw std::runtime_error("Memory allocation failed in LargeData copy assignment operator");}return *this;}LargeData(LargeData&& other) noexcept : dataSize(0), data(nullptr){*this = std::move(other);}LargeData& operator=(LargeData&& other) noexcept{if (this == &other) {return *this;}delete[] data;data = other.data;dataSize = other.dataSize;other.data = nullptr;other.dataSize = 0;return *this;}
private:size_t dataSize;char* data;
};

三、零之法则

1、概念

        C++的零之法则是指,如果可能,类应该避免声明任何特殊成员函数。鼓励让编译器自动生成这些特殊成员函数,以简化类的设计和管理。

2、使用场景

简化设计:零之法则通过减少需要编写的代码量,简化类的设计。当类不需要显式管理资源时,遵循零之法则可以使类的接口更加清晰。

减少错误:手动编写特殊成员函数容易引入错误,特别是当类的成员变量较多或类型复杂时。编译器生成的特殊成员函数通常更加健壮。

利用标准库:零之法则鼓励使用标准库组件(如std::string、std::vector等)来管理资源。

提高可维护性:遵循零之法则的类更加简洁,更易于理解和维护。

3、如何实现

避免显式声明特殊成员函数:除非类需要显式管理资源,否则让编译器自动生成这些函数。

使用组合而非继承:组合优于继承是面向对象设计中的一个重要原则。通过组合,可以将其它类的实例作为当前类的成员变量,从而避免复杂的继承关系和虚函数的开销。

利用智能指针:对于需要动态分配内存的场景,使用C++11及以后版本中引入的智能指针(如std::unique_ptr、std::shared_ptr等)。这些智能指针可以自动管理内存,减少内存泄漏的风险。

4、示例

4.1 合理使用C++标准库管理内存

4.2中类使用标准库函数重新实现,对象的内存管理交由标准库进行处理。

class LargeData {
public:LargeData() = default;explicit LargeData(size_t dataSize) : data(dataSize, '\0') {}private:std::string data; // 使用std::string来存储数据
};
关键字:河北省建设工程招标网_申请163 com免费邮箱_优化大师安卓版_百度搜索指数排名

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: