承德网站开发公司,网站开发的目的实习报告,校园网站建设初探,需要留电话号码的广告系统中的动态资源、文件句柄#xff08;socket描述符、文件描述符#xff09;是有限的#xff0c;在类中若涉及对此类资源的操作#xff0c;但是未做到妥善的管理#xff0c;常会造成资源泄露问题#xff0c;严重的可能造成资源不可用。或引发未定义行为#xff0c;进而… 系统中的动态资源、文件句柄socket描述符、文件描述符是有限的在类中若涉及对此类资源的操作但是未做到妥善的管理常会造成资源泄露问题严重的可能造成资源不可用。或引发未定义行为进而引起程序崩溃、表现出意外的行为、损坏数据或者可能看似正常工作但在其它情况下出现问题。 三之法则和五之法则可以很好地解决上述问题。它们帮助开发者理解和管理类的拷贝控制操作避免常见的资源泄露、重复释放等问题并优化程序的性能。零之法则关注类的特殊成员函数的声明和使用。在实际开发中应根据类的具体需求来决定是否需要自定义这些特殊的成员函数。
一、三之法则
1、概念 三之法则也称为“三大定律”或“三法则”它指出如果类定义了以下三个特殊成员函数之一析构函数、拷贝构造函数或拷贝赋值运算符则开发者通常也需要定义其它两个特殊成员函数以确保类的拷贝控制和资源管理行为的正确性。
2、使用场景 三之法则主要是为了避免资源泄露、重复释放或其它由于浅拷贝导致的错误。 默认情况下编译器会为类生成默认的析构函数、拷贝构造函数和拷贝赋值运算符但这些默认实现通常只进行浅拷贝即只复制对象的成员变量的值而不复制成员变量所指向的资源。如果成员变量是指针并且指向动态分配的内存则浅拷贝会导致两个对象共享同一块内存从而在销毁时发生重复释放的错误。
3、如何实现
定义所有需要的特殊成员函数如果类需要自定义其中一个特殊成员函数那么通常也需要自定义其他两个成员函数以确保对象的拷贝和赋值行为符合预期。
理解资源管理了解类所管理的资源并决定是否需要自定义特殊成员函数来管理这些资源的拷贝和赋值。
使用RAII将资源的生命周期与对象的生命周期绑定简化资源管理降低资源泄露风险。
4、示例
4.1 类中包含指针私有成员
#include iostream
#include cstringclass Point {
public: Point(size_t n 0) : numCoords(n), coords(n ? new double[n] : nullptr){if (coords) {std::memset(coords, 0, n * sizeof(double));}}~Point(){delete[] coords;}Point(const Point other) : numCoords(other.numCoords), coords(new double[other.numCoords]){std::memcpy(coords, other.coords, numCoords * sizeof(double));}Point operator(const Point other){ if (this ! other) {delete[] coords;numCoords other.numCoords;coords new double[numCoords];std::memcpy(coords, other.coords, numCoords * sizeof(double));}return *this;} private: double *coords; size_t numCoords;
}; int main()
{Point p1(3);Point p2 p1;Point p3;p3 p1;return 0;
}
二、五之法则
1、概念 五之法则在C11及以后版本引入它在三之法则的基础上增加了两个新的特殊成员函数移动构造函数和移动赋值运算符以支持移动语义。
2、使用场景 五之法则的引入是为了进一步提高程序的性能特别是在处理大型对象或资源密集型对象时。通过允许对象之间的资源移动而不是复制可以减少不必要的内存分配和释放操作从而提高程序的运行效率。
3、如何实现
定义所有五个特殊成员函数如果类需要移动语义则应该定义所有五个特殊成员函数析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符。
使用noexcept关键字在C11及以后版本中移动构造函数和移动赋值运算符通常会被标记为noexcept表明它们不会抛出异常。这有助于编译器优化代码并允许在更多情况下使用移动语义。
理解移动语义了解移动语义的工作原理并决定何时以及如何使用它来优化程序的性能。
4、示例
4.1 socket描述符
#include iostream
#include cstdio
#include cstring
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include stdexceptclass SocketDescriptor {
public:// 默认构造函数创建一个无效的socketSocketDescriptor() : fd(-1) {}// 构造函数接受一个已创建的socket文件描述符explicit SocketDescriptor(int socket_fd) : fd(socket_fd){if (fd -1) {throw std::runtime_error(Invalid socket descriptor);}}// 析构函数关闭socket文件描述符 ~SocketDescriptor(){closeSocket();}// 禁用拷贝构造函数SocketDescriptor(const SocketDescriptor) delete;// 禁用拷贝赋值运算符SocketDescriptor operator(const SocketDescriptor) delete; // 移动构造函数SocketDescriptor(SocketDescriptor other) noexcept : fd(other.fd){other.fd -1; // 将原对象的fd置为无效}// 移动赋值运算符SocketDescriptor operator(SocketDescriptor other) noexcept{if (this ! other) {closeSocket(); // 关闭当前对象的fdfd other.fd; // 转移资源other.fd -1; // 将原对象的fd置为无效}return *this;}// 获取socket文件描述符int get() const{return fd;}// 创建一个新的socket并管理它static SocketDescriptor createSocket(int domain, int type, int protocol){int fd socket(domain, type, protocol);if (fd -1) {throw std::runtime_error(Failed to create socket);}return SocketDescriptor(fd);}// 关闭socket文件描述符
private:void closeSocket(){if (fd ! -1) {::close(fd);fd -1;}}private:int fd;
};int main()
{try {// 创建一个新的TCP socketSocketDescriptor sock SocketDescriptor::createSocket(AF_INET, SOCK_STREAM, 0);// 打印socket文件描述符std::cout Socket descriptor: sock.get() std::endl; } catch (const std::exception e) {std::cerr Error: e.what() std::endl;}return 0;
}
4.2 拷贝大数据
#include iostream
#include algorithm // 用于std::copy
#include stdexcept // 用于std::runtime_error 和 std::bad_alloc 的异常处理class LargeData {
public:LargeData() default;// 构造函数接受数据大小并初始化数据explicit LargeData(size_t dataSize){try {data new char[dataSize];this-dataSize dataSize;std::fill_n(data, dataSize, 0); // 初始化数据为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; // 这里实际上不应该这样做因为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;}// 获取数据大小size_t getSize() const{return dataSize;}// 获取数据指针注意返回的指针不应被删除const char* getData() const{return data;}// 非const版本的getData用于修改数据通常不推荐这样做但为了完整性而提供char* getData(){return data;}// 设置数据示例函数用于修改数据内容注意大小必须匹配void setData(const char* newData, size_t newSize){if (newSize ! dataSize) {throw std::runtime_error(New data size does not match existing data size);}std::copy(newData, newData newSize, data);}private:size_t dataSize; // 数据大小char* data; // 指向动态分配数据的指针
};int main()
{try {// 创建大量数据对象LargeData data1(1024); // 1KB数据// 使用拷贝构造函数LargeData data2 data1; // data2是data1的深拷贝// 使用拷贝赋值运算符LargeData data3;data3 data1; // data3现在是data1的深拷贝// 清理资源由析构函数自动处理} catch (const std::exception e) {std::cerr Exception: e.what() std::endl;}return 0;
}
三、零之法则
1、概念 C的零之法则是指如果可能类应该避免声明任何特殊成员函数。鼓励让编译器自动生成这些特殊成员函数以简化类的设计和管理。
2、使用场景
简化设计零之法则通过减少需要编写的代码量简化类的设计。当类不需要显式管理资源时遵循零之法则可以使类的接口更加清晰。
减少错误手动编写特殊成员函数容易引入错误特别是当类的成员变量较多或类型复杂时。编译器生成的特殊成员函数通常更加健壮。
利用标准库零之法则鼓励使用标准库组件如std::string、std::vector等来管理资源。
提高可维护性遵循零之法则的类更加简洁更易于理解和维护。
3、如何实现
避免显式声明特殊成员函数除非类需要显式管理资源否则让编译器自动生成这些函数。
使用组合而非继承组合优于继承是面向对象设计中的一个重要原则。通过组合可以将其它类的实例作为当前类的成员变量从而避免复杂的继承关系和虚函数的开销。
利用智能指针对于需要动态分配内存的场景使用C11及以后版本中引入的智能指针如std::unique_ptr、std::shared_ptr等。这些智能指针可以自动管理内存减少内存泄漏的风险。
4、示例
4.1 合理使用C标准库管理内存
#include iostream
#include stdexcept
#include stringclass LargeData {
public:// 默认构造函数创建一个空的LargeData对象LargeData() default;// 构造函数接受数据大小以字节为单位并初始化一个相应大小的空字符串explicit LargeData(size_t dataSize) : data(dataSize, \0) {}// 获取数据大小以字节为单位size_t getSize() const{return data.size();}// 获取数据返回const char*以避免修改数据const char* getData() const{return data.c_str();}// 非const版本的getData用于修改数据通常不推荐这样做但为了完整性而提供// 注意这允许调用者修改数据但他们必须确保不超出字符串的边界。char* getData() {return data[0];}// 设置数据注意大小必须匹配否则抛出异常void setData(const char* newData, size_t newSize){if (newSize ! data.size()) {throw std::runtime_error(New data size does not match existing data size);}data.assign(newData, newSize);}// 为了方便添加一个接受std::string的setData重载void setData(const std::string newData){if (newData.size() ! data.size()) {throw std::runtime_error(New data size does not match existing data size);}data newData;}private:std::string data; // 使用std::string来存储数据
};int main()
{try {// 创建LargeData对象大小为1024字节LargeData data1(1024);// 使用拷贝构造函数由编译器隐式生成LargeData data2 data1; // data2是data1的一个深拷贝因为std::string是深拷贝的// 使用拷贝赋值运算符由编译器隐式生成LargeData data3;data3 data1; // data3现在是data1的一个深拷贝// 注意不需要手动清理资源因为std::string会处理它} catch (const std::exception e) {std::cerr Exception: e.what() std::endl;}return 0;
}4.2 智能指针管理资源
#include iostream
#include memory
#include vector// 一个简单的类表示资源
class Resource {
public:Resource(int value) : value_(value){std::cout Resource created with value: value_ std::endl;}~Resource(){std::cout Resource destroyed with value: value_ std::endl;}void display() const{std::cout Resource value: value_ std::endl;}
private:int value_;// 禁止拷贝和赋值Resource(const Resource) delete;Resource operator(const Resource) delete;
};// 一个管理类使用智能指针管理资源
class ResourceManager {
public:void addResource(int value){// 使用std::make_unique来创建unique_ptrresources_.push_back(std::make_uniqueResource(value));}void displayAllResources() const{for (const auto resource : resources_) {resource-display();}}private:std::vectorstd::unique_ptrResource resources_;
};int main()
{// 创建ResourceManager对象ResourceManager manager;// 添加资源manager.addResource(10);manager.addResource(20);manager.addResource(30);// 显示所有资源manager.displayAllResources();// ResourceManager对象离开作用域时其析构函数会调用所有unique_ptr会自动释放资源return 0;
}