文章标题 原创 翻译 转载 文章内容 我们知道new一个对象要自己去delete,这样的代码写多了很容易漏掉,常见漏掉的情况如下: * 忘了写 * 写了,但是某个分支提前return了 * 逻辑中抛出异常,直接跳过正常逻辑 假设有这样一个简单的类: ``` class ConnTest { public: ConnTest() { std::cout << "ConnTest" << std::endl; } ~ConnTest() { std::cout << "~ConnTest" << std::endl; } void open() { std::cout << "open" << std::endl; } void close() { std::cout << "close" << std::endl; } void doSomething() { std::cout << "do something" << std::endl; } static ConnTest* create() { return new ConnTest(); } }; ``` 简单用法: ``` ConnTest *conn = ConnTest::create(); if (conn) { conn->open(); conn->doSomething(); conn->close(); delete conn; } ``` 这里逻辑简单清晰不太会出什么问题,一旦逻辑复杂了怎么办呢? # RAII惯用法 我们可以自己写个类来保证对象在出作用域的时候销毁 ``` class ConnTestGuard { public: ConnTestGuard(ConnTest *p) : p_(p) {} ~ConnTestGuard() { if (p_) delete p_; } private: ConnTest *p_; }; ``` 用法: ``` ConnTestGuard connGuard(ConnTest::create()); ``` 这样失去了if判断和指针对函数的调用,可能要写个get()方法获取原始指针,或者直接把原始指针暴露出去 ``` ConnTest *conn = ConnTest::create(); ConnTestGuard connGuard(conn); ``` 这样写多出一行不说,conn单独在外面很有可能不明真相的人会去调用delete conn。 那怎样保证原始调用的代码不变又能自动销毁,需要在ConnTestGuard中实现两个操作符 ``` class ConnTestGuard { public: ConnTestGuard(ConnTest *p) : p_(p) {} ~ConnTestGuard() { if (p_) delete p_; } ConnTest* operator->() const { return p_; } operator bool() const { return !!p_; } private: ConnTest *p_; }; ``` 用法: ``` ConnTestGuard conn(ConnTest::create()); if (conn) { conn->open(); conn->doSomething(); conn->close(); } ``` 这样就不用delete,当然如果有需要你也可以把close放在ConnTestGuard析构函数里面。 但是上面的类不通用,该std::unique_ptr上场了。 # std::unique_ptr std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针 ``` std::unique_ptr<ConnTest> conn(new ConnTest()); if (conn) { conn->open(); conn->doSomething(); conn->close(); } ``` 默认情况下它只负责delete,那close怎么办,这里只是举个例子通常close我们会写在ConnTest析构函数里面,如果确实有这个需求unique_ptr可以自定义销毁函数 ``` // 用法一: auto connTestDelete = [](ConnTest *conn) { if (conn) { conn->close(); delete conn; } }; std::unique_ptr<ConnTest, decltype(connTestDelete)> p(new ConnTest()); // 用法二: std::unique_ptr<ConnTest, std::function<void(ConnTest*)>> p(new ConnTest(), [](ConnTest* p) { if (p) { p->close(); delete p; } }); ``` 是不是有点麻烦,这里用到了c++11的一些特性,如果不用c++11会更麻烦些。所以把该清理的动作还是放在析构函数里吧,这样只需要delete,它会在析构函数里面帮你清理所有资源。 你也可以直接返回unique_ptr对象,这样用户一看就明白了,也不用关系资源的销毁 ``` static std::unique_ptr<ConnTest> create() { std::unique_ptr<ConnTest> p(new ConnTest()); return p; } ``` 文章类别 Python Mobile Android Java Shell Life Database Bug Windows IOS Tools Boost Node.js Mac Product Tips C/C++ Golang Javascript React Qt MQ MongoDB Design Web Linux LLM ChatGPT RAG AI 提交