【问题标题】:How to avoid dangling pointer with shared_ptr?如何避免使用 shared_ptr 悬空指针?
【发布时间】:2019-11-19 03:26:45
【问题描述】:

我有一个类,其对象指针将作为键/数据添加到多个 std::map/std::unordered_map/hash(内部实现)中。为了自动删除我正在使用 shared_ptr 的对象。

我使用shared_ptr only 类设计了我的课程。

现在我想确保将来没有人这样做:

#include <memory>
#include <string>

class A {
 protected:
   struct this_is_private;

 public:
   explicit A(const this_is_private &) {}
   A(const this_is_private &, ::std::string, int) {}

   template <typename... T>
   static ::std::shared_ptr<A> create(T &&...args) {
      return ::std::make_shared<A>(this_is_private{0},
                                   ::std::forward<T>(args)...);
   }

 protected:
   struct this_is_private {
       explicit this_is_private(int) {}
   };

   A(const A &) = delete;
   const A &operator =(const A &) = delete;
};


::std::map<A*, int> m_error;
::std::map<::std::shared_ptr<A>, int> m_ok;

::std::shared_ptr<A> foo()
{
   ::std::shared_ptr<A> temp = A::create();

   A * obj_ptr = temp.get(); 
   m_error.insert(pair<A*, int>(obj_ptr, 10)); //How to make sure no one do this in future
   m_ok.insert(pair<::std::shared_ptr<A>, int>(temp,10)); //ok
}

【问题讨论】:

  • 人们总是能够创建指向现有对象的原始指针。如果不使用 create 函数,您已经无法创建 A。如果有人真的想朝自己的脚开枪,你无法阻止他们。
  • 就像 super 所说的那样,你无法阻止人们朝自己的脚开枪。不过,您可以做的一件事是拥有良好的代码审查流程,并要求在创建指针时使用 make_uniquemake_shared
  • 您可以编写一个包装类来包装内部存储的shared_ptr&lt;A&gt;。但最终人们也可以获取/存储 that 的指针。更不用说您可以轻松地存储对A 的引用(只需取消对shared_ptr&lt;A&gt; 的引用),这更不可能避免。
  • 为了澄清你的问题:更一般地说,你想防止人们获得指向你的 A 类对象的原始指针吗?
  • 您可以为您的班级deleteoperator&amp; 以防止任何人以简单的方式获取其地址,但也有std::addressof,当然还有std::shared_ptr&lt;A&gt;::get(以及其他10 个方式),你不可能阻止。 godbolt.org/z/vDw801

标签: c++ c++11 memory-management stl smart-pointers


【解决方案1】:

如何避免 shared_ptr 悬空指针?

根本不存储指向对象的裸指针(也不是引用或迭代器),或者确保此类指针的生命周期比共享指针的生命周期短。后者的正确性不如前者的正确性那么容易证明。

如何确保将来没有人[存储裸指针]

除了完全封装对对象的访问之外,C++ 语言中没有任何功能可以阻止获取对象的地址并存储它。获取地址的程序员始终有责任确保生命周期是 - 并且将来会是 - 他们所期望的。改变对象生命周期的程序员有责任确保改变的生命周期不依赖任何东西。

有编程languages,被设计成不让程序员直接访问对象的地址,从而使此类错误成为不可能。 C++ 不是其中一种语言。

【讨论】:

    【解决方案2】:

    将您的 shared_ptr 隐藏在包装类“HiddenSharedPointer”中,这样​​您的类的用户就无法直接访问该对象。

    #include <memory>
    #include <list>
    #include <utility>
    
    class A 
    {
    public:
        class HiddenSharedPointer : private std::shared_ptr<A>
        {
        public:
            template<typename... Args>
            explicit HiddenSharedPointer(Args&&... args);
            HiddenSharedPointer(HiddenSharedPointer&) = default;
            HiddenSharedPointer(const HiddenSharedPointer&) = default;
            HiddenSharedPointer(HiddenSharedPointer&&) = default;
            HiddenSharedPointer& operator=(const HiddenSharedPointer&) = default;
            HiddenSharedPointer& operator=(HiddenSharedPointer&&) = default;
    
            // methods directly called on the underlying shared_ptr
            using std::shared_ptr<A>::reset;
    
            // methods called on the object referenced by the underlying shared_ptr
            void foo();
        };
    private:
        explicit A()
        {}
        A(::std::string, int)
        {}
        A(const A &) = delete;
        const A &operator =(const A &) = delete;
    public:
        template <typename... T>
        static HiddenSharedPointer create(T &&...args)
        {
            return HiddenSharedPointer(::std::forward<T>(args)...);
        }
    
        void foo()
        {
        }
    };
    
    void A::HiddenSharedPointer::foo()
    {
        std::shared_ptr<A>(*this)->foo();
    }
    
    template<typename... Args>
    A::HiddenSharedPointer::HiddenSharedPointer(Args&&... args)
        : std::shared_ptr<A>(new A(std::forward<Args>(args)...))
    {}
    
    std::list<std::pair<A::HiddenSharedPointer, int>> m_ok;
    
    int main()
    {
        A::HiddenSharedPointer temp = A::create();
        temp.foo();
        //auto plain_pointer_to_object = temp.get(); // does not compile
        m_ok.push_back(std::pair<A::HiddenSharedPointer, int>(temp, 10));
    
        temp.reset();
    
        return 0;
    }
    

    请注意,我将地图更改为对列表,因为如果使用地图,您必须为 HiddenSharedPointer 类提供 operator

    【讨论】:

    • 您可以实现operator -&gt; 以返回底层指针,但会立即在其上重新应用-&gt;。这比隐藏每个方法的工作量要少,但我接受它确实为GetThis() 方法留出了空间,这将暴露实际的原始指针。 GetThis() any 看起来很傻,但GetAsAThingy() 是避免dynamic_cast&lt;&gt; 成本的常用方法。
    猜你喜欢
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    • 2011-10-01
    • 2016-09-17
    • 2020-07-24
    • 2017-03-21
    • 2015-01-25
    • 1970-01-01
    相关资源
    最近更新 更多