【Effective C++】3. 资源管理

文章讲述了在C++编程中如何通过RAII原则、智能指针(如unique_ptr和shared_ptr)以及小心的复制行为来有效管理内存资源,避免资源泄漏。特别强调了std::make_shared的使用以确保新对象和调用顺序的正确性。
摘要由CSDN通过智能技术生成

Item13 以对象管理资源

资源:

  • 内存、文件描述符、互斥锁、数据库链接和网络socket等,用了需要还给操作系统
  • 异常、函数多重返回路径和程序员不恰当的改动,会导致资源没有释放

考虑以下场景,delete都不会执行,资源都会泄漏,

  • 如果在do_something过程出现return;
  • 或者do_something过程抛出异常;
  • 或者delete语句在循环内,被continue;
class Investment {...}; // 投资行为的父类
Investment* creatInvestment(); // 创建对象的工厂
void f() {
    Investment* pInv = creatInvestment();
    // do_something
    delete pInv;    
}

解决思路,以对象管理资源:

  • 获得资源后立即放进管理对象内(RAII,Resource Acquisition Is Initialization);
  • 管理对象运用析构函数自动调用的机制确保资源被释放。

上述函数可以使用智能指针的方式修改

#include <memory>
class Investment {};
Investment* creatInvestment() {
    return new Investment;
}
void f() {
    // std::auto_ptr<Investment> pInv(creatInvestment()); // c++98标准
    std::shared_ptr<Investment> pInv(creatInvestment());  // c++11标准
}


/*
 * auto_ptr 为了防止同一个对象被删除多次,通过拷贝和或者拷贝赋值,该指针会变为null
 * 与stl容器要求的正常复制行为不兼容
 * c++11 中unique_ptr会静止拷贝操作 
int main() {
    std::auto_ptr<Investment> pInv1(creatInvestment());
    std::auto_ptr<Investment> pInv2(pInv1); 
    if (pInv1.get() == nullptr) std::cout << "pInv1 is null\n";
    if (pInv2.get() != nullptr) std::cout << "pInv2 is not null\n";

    return 0;
}
*/

注意事项:不能用来管理动态分配的数组,因为底层调用的时delete,而不是delete []

int main() {
    std::shared_ptr<std::string> aps(new std::string[10]);
    return 0;
}
/*
 * 报错内容
munmap_chunk(): invalid pointer
Aborted (core dumped)
*/

Item14 在资源管理类中小心copying行为

当一个RAII对象被复制,常用的处理方式:

  • 禁止复制,如:std::unique_lock
  • 对底层资源采用”引用计数法“:如:std::shared_ptr
    • 还可以指定自定义的删除函数
class newWidget { };
void customDeleter(newWidget* ptr) {
    std::cout << "Custom deleter is called" << std::endl;
    delete ptr;
}
std::shared_ptr<newWidget> ptr(new newWidget, customDeleter);

Item15 在资源管理类中提供对原始资源的访问

针对有些API需要访问原始资源的需求:

  • 访问原始资源三种方式:pInv.get()、operator->和operator*;
  • 自定义RAII对象可以提供显示转换函数和隐式转换(仿函数的方式)。
class FontHandle {};
FontHandle getFont() { 
    FontHandle fh;
    return fh;
}
class Font {
public:
    explicit Font(FontHandle fh) : f(fh) {}
    FontHandle get() const { std::cout << "显示转换\n"; return f; } 
    operator FontHandle() const { std::cout << "隐式转换\n"; return f; } 
private:
    FontHandle f;
};
void changeFontSize(FontHandle f) {}

int main() {
    // int days = daysHeld(pInv); // 编译失败
    int days = daysHeld(pInv.get());
    bool isTaxFree = pInv->isTaxFree(); 
    bool isTaxFree2 = (*pInv).isTaxFree();

    Font f1(getFont());
    changeFontSize(f1.get());
    Font f2(getFont());
    changeFontSize(f2);

    return 0;
}

Item16 成对使用new和delete时采用相同的形式

使用new动态生成一个对象,分为两个步骤:

  1. 分配内存(调用operator new)
  2. 在内存上调用构造函数

使用delete删除对象,也分为两个步骤:

  1. 在内存上调用析构函数
  2. 释放内存(调用operator delete)
  • 使用delete删除对象需要知道内存中有多少个对象,决定了有多少个析构函数调用;
  • 内存模型中需要保存一个变量记录数组大小,delete加上[]让编译器知道调用多少个析构函数

注意:不要对数组形式做typedef动作,很容易出现不对称的行为。

Item17 以独立语句将new对象放入智能指针

考虑以下场景:

class Widget {};
int priority() {return 0;}
void processWidget(std::shared_ptr<Widget> pw, int priority) {}

int main() {
    // processWidget(new Widget, priority()); //  error: could not convert ‘(Widget*)operator new(1)’ from ‘Widget*’ to ‘std::shared_ptr<Widget>’
    // processWidget(std::shared_ptr<Widget>(new Widget), priority());
    // std::shared_ptr<Widget> pw(new Widget);
    // processWidget(pw, priority());
    processWidget(std::make_shared<Widget>(), priority());
    return 0;
}

processWidget函数可能的执行顺序(取决于编译器,保证1在3前面,2的执行顺序不确定):

  1. 执行 new Widget;
  2. 调用 priority();
  3. 调用 std::shared_ptr 构造函数

当调用 priority() 发生异常,new Widget 返回的指针将丢失,资源不能被正确释放

解决方案:

  • 以独立的语句将对象存储在智能指针内;
  • C++11标准中可以使用 std::make_shared<Widget>() 合并两个过程。
  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨主任o_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值