【问题标题】:shared_ptr that cannot be null?shared_ptr 不能为空?
【发布时间】:2017-06-16 18:01:31
【问题描述】:

使用std::shared_ptr 表示共享所有权和可选性(可能为空)。

我发现自己只想在我的代码中表达共享所有权,而没有可选性。当使用shared_ptr 作为函数参数时,我必须让函数检查它是否不为空以保持一致/安全。

在许多情况下,传递参考而不是当然是一种选择,但我有时也想转移所有权,因为shared_ptr 是可能的。

是否有一个类可以替换 shared_ptr 而不可能为空,有一些约定来处理这个问题,或者我的问题没有多大意义?

【问题讨论】:

  • 传递一个 (const) 引用。
  • ^ 按值传递shared_ptr 的用例并不多。
  • 你真的想解决什么问题?如果你觉得你的shared_ptr 永远不会为空,那么只需记录并断言这个假设并继续前进。你确实会测试你的代码,对吧?
  • 我当然可以记录和断言。我说的是表现力和使用类型系统来防止编译时出现一种错误。此外,它会将每个函数所需的测试用例数量减少一个,即通过 nullptr 的情况。 ;-)

标签: c++ smart-pointers null-pointer invariants preconditions


【解决方案1】:

查看GSL's not_null 类后,它调用std::terminate() 而不是abort()

我是这样实现的:

template <typename T>
class NonNull : public std::shared_ptr<T> {
    typedef std::shared_ptr<T> super;
public:
    inline NonNull()
        : super(new T())
    {
        if ( ! super::get()) {
            abort(); // Out of memory.
        }
    }

    inline explicit NonNull(T *ptr)
        : super(ptr)
    {
        if ( ! super::get()) {
            abort(); // Input was null.
        }
    }
}

基本上,强制我们构造T类型的类。

用法:

// Directly is a `std::shared_ptr` type:
NonNull<MyClass> myVariable;

// Unlike:
gsl::not_null<std::shared_ptr<MyClass > > myVariable;

【讨论】:

    【解决方案2】:

    您要求not_null 包装类。幸运的是,C++ 专家guideline 已经解决了您的问题,并且已经有示例实现——例如one。搜索not_null 类模板。

    【讨论】:

    • 在演讲中:CppCon 2016: Neil MacIntosh “The Guideline Support Library: One Year later”,据说not_null 是为原始指针设计的,不一定适合智能指针。你知道吗?有更新的信息吗?
    • @generic_opto_guy 它适用于 shared_ptr,所以为什么不使用。我可以接受对于 unique_ptr not_null 不是最好的——因为在“移动”之后它将为空——这真的是不可接受的。
    【解决方案3】:

    您可以围绕std::shared_ptr 编写一个仅允许从非空创建的包装器:

    #include <memory>
    #include <cassert>
    
    template <typename T>
    class shared_reference
    {
        std::shared_ptr<T> m_ptr;
        shared_reference(T* value) :m_ptr(value) { assert(value != nullptr);  }
    
    public:
        shared_reference(const shared_reference&) = default;
        shared_reference(shared_reference&&) = default;
        ~shared_reference() = default;
    
        T* operator->() { return m_ptr.get(); }
        const T* operator->() const { return m_ptr.get(); }
    
        T& operator*() { return *m_ptr.get(); }
        const T& operator*() const { return *m_ptr.get(); }
    
        template <typename XT, typename...XTypes>
        friend shared_reference<XT> make_shared_reference(XTypes&&...args);
    
    };
    
    
    template <typename T, typename...Types>
    shared_reference<T> make_shared_reference(Types&&...args)
    {
        return shared_reference<T>(new T(std::forward<Types>(args)...));
    }
    

    请注意,operator= 尚未丢失。您绝对应该添加它。

    你可以这样使用它:

    #include <iostream>
    
    
    using std::cout;
    using std::endl;
    
    struct test
    {
        int m_x;
    
        test(int x)         :m_x(x)                 { cout << "test("<<m_x<<")" << endl; }
        test(const test& t) :m_x(t.m_x)             { cout << "test(const test& " << m_x << ")" << endl; }
        test(test&& t)      :m_x(std::move(t.m_x))  { cout << "test(test&& " << m_x << ")" << endl; }
    
        test& operator=(int x)          { m_x = x;                  cout << "test::operator=(" << m_x << ")" << endl; return *this;}
        test& operator=(const test& t)  { m_x = t.m_x;              cout << "test::operator=(const test& " << m_x << ")" << endl; return *this;}
        test& operator=(test&& t)       { m_x = std::move(t.m_x);   cout << "test::operator=(test&& " << m_x << ")" << endl; return *this;}
    
        ~test()             { cout << "~test(" << m_x << ")" << endl; }
    };
    
    #include <string>
    
    int main() {
    
        {
            auto ref = make_shared_reference<test>(1);
            auto ref2 = ref;
    
            *ref2 = test(5);
        }
        {
            test o(2);
            auto ref = make_shared_reference<test>(std::move(o));
        }
    
        //Invalid case
        //{
        //  test& a = *(test*)nullptr;
        //  auto ref = make_shared_reference<test>(a);
        //}
    }
    

    输出:

    test(1)
    test(5)
    test::operator=(test&& 5)
    ~test(5)
    ~test(5)
    test(2)
    test(test&& 2)
    ~test(2)
    ~test(2)
    

    Example on Coliru

    我希望我没有忘记任何可能导致未定义行为的事情。

    【讨论】:

    • 谢谢。正是我的意思。但是为什么它需要一个自定义的operator=?默认的似乎工作正常。
    • 因为rule-of-five.
    • 好的,我明白规则了。为什么在这种情况下需要声明函数并默认它们?我想你的意思是like that。我们能不能简单地将它们完全排除在外like this。或者这是否有我还没有看到的负面影响?
    猜你喜欢
    • 2020-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多