使用std智能指针的正确方法来确保ptr安全

Posted

技术标签:

【中文标题】使用std智能指针的正确方法来确保ptr安全【英文标题】:Correct way to use std smart pointers to ensure ptr safety 【发布时间】:2015-11-14 13:36:44 【问题描述】:

这是使用std智能指针确保ptr安全的正确方法吗

这个例子可能不是最好的,但我试图模拟一些真实的代码。我遇到的问题是在实际代码中,通信器指针是一个原始指针,可以随时解除分配 - 导致使用指针时崩溃。

所以我决定研究 std::shared_ptr 和 std::weak_ptr 看看现在我们有 C++11 应该如何设计它。我在发送代码中使用了一个weak_ptr,它检查ptr是否仍然有效,然后才会取消引用ptr。这段代码是正确的方法吗?有什么改进吗?

#include <memory>
#include <iostream>
#include <string>

class communicator

public:
    communicator(const char* name, int comport, int speed) : name_(name), comport_(comport), speed_(speed)  

    void send(const std::string& s) 
        std::cout << "sending " << s << " using " << name_ << " at " << speed_ << " rate and using com port " << comport_ << '\n';
    

private:
    const char* name_;
    int comport_;
    int speed_;
;

class sender

public:
    sender() 

    void set_communicator(std::weak_ptr<communicator> comms) 
        comms_ = comms;
    

    void send(const std::string& s)
    
        if (auto sh = comms_.lock())
            sh->send(s);
        else
            std::cout << "Attempting to send: " << s << " but ptr no longer exists\n";
    

private:
    std::weak_ptr<communicator> comms_;
;

int main() 

    sender mysender;

    
        // create comms object
        std::shared_ptr<communicator> comms(new communicator("myname", 3, 9600));

        mysender.set_communicator(comms);

        mysender.send("Hi guys!");

      // comms object gets deleted here

    mysender.send("Hi guys after ptr delete!");

输出:

sending Hi guys! using myname at 9600 rate and using com port 3
Attempting to send: Hi guys after ptr delete! but ptr no longer exists

【问题讨论】:

可能更适合codereview。 我会使用std::make_shared 【参考方案1】:

可以随时解除分配的指针 - 导致 使用指针崩溃

这就是引入weak_ptr 的基本原理背后的症状;因此,我认为您的基于 weak_ptr 的方法是正确的。

然而,我觉得值得商榷的是,与此相结合

sender() : comms_() 

void set_communicator(std::weak_ptr<communicator> comms) 
    comms_ = comms;

sender 的内部资产comms_ 的两阶段构建 一旦 lock() 失败,您就不会将内部资产的状态重置为构建后状态

void send(const std::string& s)

但这本身并没有“错误”;这只是可以考虑用于完整应用程序的东西。

另一件事是当lock() 失败时您不会throw(或let throw by the shared_ptr(weak_ptr) ctor (#11)),而只需if-else 处理它。我不知道您的完整应用程序的要求,但根据您组装的提取物,基于异常的错误处理将改进 imo 的设计。

例如:

#include <memory>
#include <stdexcept>
#include <iostream>
#include <string>

class communicator

public:
    communicator(const char* name, int comport, int speed) 
        : name_(name), comport_(comport), speed_(speed)  

    void send(const std::string& s) 
        std::cout << "sending " << s << " using " << name_ << " at " 
                  << speed_ << " rate and using com port " << comport_ 
                  << '\n';
    

private:
    const char* name_;
    int comport_;
    int speed_;
;

class sender

public:
    struct invalid_communicator : public std::runtime_error 
        invalid_communicator(const std::string& s) :
            std::runtime_error(
                std::string("Attempting to send: \"") + s 
                    + "\" but communicator is invalid or not set"
            ) 
    ;  

    sender() : comms_() 

    void set_communicator(std::weak_ptr<communicator> comms) 
        comms_ = comms;
    

    /* non-const */
    void send(const std::string& s) throw (invalid_communicator)
    
        try 
            auto sh = std::shared_ptr<communicator>(comms_);
            sh->send(s);
         catch (const std::bad_weak_ptr& e) 
            comms_ = decltype(comms_)();
            throw invalid_communicator(s);
        
    

private:
    std::weak_ptr<communicator> comms_;
;

int main() 
    int rv = -1;
    sender mysender;

    for (auto com : 1, 2, 3) 
        try 
             
                // create comms object
                auto comms = std::make_shared<communicator>(
                    "myname", com, 9600
                );
                mysender.set_communicator(comms);
                mysender.send("Hi guys!");
            // comms object gets deleted here

            mysender.send("Hi guys after ptr delete!"); 

            // never reached in this example; just to illustrate
            // how the story could continue  
            rv = EXIT_SUCCESS;            
            break; // it'd be not nice to "break", but I did not want to
                   // introduce another state variable
         catch (const sender::invalid_communicator& e) 
            std::cerr << e.what() << std::endl;
        
    

    return rv;

live Coliru 的

【讨论】:

【参考方案2】:

这是使用std智能指针确保ptr安全的正确方法吗

除了 decltype_auto 提到的内容之外,我可以补充一点,使用weak_ptr 的原因之一是为了防止循环依赖。如果不存在这种可能性,您最好将其共享,这将使 send 的实现更不容易出错,除非通信通道的生命周期确实是临时的。

您可以隐藏它们在实现中存在各种连接或会话的事实。

【讨论】:

【参考方案3】:

此外,在使用标准智能指针设计接口/API 时,请考虑使用更受限制的指针,例如 unique_pointer。

这样的指针非常清楚地传达了意图 - 例如通过将唯一指针作为函数的参数,您可以清楚地告诉用户他正在将指向资源的所有权交给被调用的函数。

【讨论】:

以上是关于使用std智能指针的正确方法来确保ptr安全的主要内容,如果未能解决你的问题,请参考以下文章

C++智能指针

智能指针是否线程安全

智能指针unique_ptr记录

[C++11新特性] 智能指针详解

智能指针std::shared_ptr总结与使用

c++11智能指针(一) shared_ptr