如何防止客户端向构造函数传递空指针?

6
通过一个期望 std::unique_ptr 的构造函数,如何有效地防止客户端传递 nullptr
class Foo{
    Foo(std::unique_ptr<Bar> bar) :
        myBar(std::move(bar))
    {}
}

我能否使用nullptr_t参数重载构造函数,然后将其设置为已删除状态,在编译时检测一些nullptr

Foo(nullptr_t) = delete;

当我已经在初始值列表中移动了nullptr时,我能否在构造函数体中安全地检查它?(有些东西告诉我我不能)

Foo(std::unique_ptr<Bar>) :
    myBar(std::move(bar))
{ 
    if(!bar)
        throw invalid_argument();
}

1
Foo(nullptr_t) = deleted; - 是的。但请记住,这只停止了nullptr,而不是空指针。 - chris
2
这将捕获 Foo{nullptr},但不会捕获 Foo{std::unique_ptr<Bar>{}} - aschepler
gcc具有nonnull属性,并将根据此属性执行一些静态分析。但我不确定在C++中它的效果如何。 - Piotr Praszmo
@iFreilicht:不,我也不建议使用引用。我建议使用一个正好符合你需要的要求的类。通常情况下,我会建议通过值存储Bar对象。但我假设你已经考虑过该选项并确定它不符合你的要求了(对吗?)。在这种情况下,似乎你需要一个类来确保其中存在唯一的Bar对象。这可能只是一个围绕unique_ptr<Bar>的包装类,它不允许出现空的unique_ptr的可能性。 - Benjamin Lindley
1
@BenjaminLindley,有趣的是,这正是我正在使用Foo类进行的操作,为了满足我的特殊需求,我将unique_ptr进行了封装。通过值传递对象的问题在于我的Bar非常大。编辑:我的Foo构造函数应该只是模拟Bar构造函数或者像make_unique一样充当转发器。 - iFreilicht
显示剩余5条评论
4个回答

6

我会结合这两种方法:

class Foo{
public:
    Foo(std::unique_ptr<Bar> bar) :
        myBar(std::move(bar))
    {
        if(!myBar)  // check myBar, not bar
            throw invalid_argument();
    }
    Foo(nullptr_t) = delete;
}

被删除的构造函数可以防止使用Foo{nullptr},但无法防止Foo{std::unique_ptr<Bar>{}},因此您还需要在构造函数体内进行检查。 但是,在move之后,您无法检查参数bar,该检查将始终失败。请检查您将unique_ptr移入的数据成员(myBar)。

哦,谢谢!那是一个非常讨厌的错误。你能说一些关于异常安全性的事情吗?我的意思是当 invalid_argument 被抛出时会发生什么?那么我们已经移动了 bar,所以甚至没有提供弱异常安全性,对吧? - iFreilicht
@iFreilicht 如果 bar 包含 nullptr,那么你才会抛出异常,因为没有资源泄漏,所以我不确定你在这种情况下所说的异常安全性是什么意思。 - Praetorian
@Praetorian,我应该如何捕获这个异常?如果我只是做try { f = Foo(bar) } catch(const std::invalid_argument& ia) { do smth }那么f就超出了作用域。这种解决方案是否意味着我不应该捕获这个异常? - LRDPRDX

3

使用= delete可以声明构造函数为删除的:

Foo(nullptr_t) = delete;

3
你可以移除nullptr_t构造函数,但这并不能捕获NULL或空的std::unique_ptr实例。你应该在构造函数内验证输入来处理后一种情况:
class Foo
{
    std::unique_ptr<Bar> myBar;

    Foo(nullptr_t) = delete;

    Foo(std::unique_ptr<Bar> bar) :
        myBar(std::move(bar))
    {
        if (myBar == nullptr)
            throw invalid_argument();
};

1
如果不需要多态行为(即您从未对 Bar 进行子类化),那么只需将 Bar 按值传递给 Foo 构造函数。否则,如果您仍然需要出于某些原因通过指针传递它,则我认为没有 100% 的绝对可靠解决方案。您可以尝试以下方法:
#include <iostream>
#include <memory>

template<typename T>
struct stricter_unique_ptr: std::unique_ptr<T> {

    // Always initialize with new object
    template<typename... A>
    stricter_unique_ptr(A&&... a):
        std::unique_ptr<T>{new T{std::forward<A>(a)...}}
    {}

    // Allow downcasting
    template<typename U>
    stricter_unique_ptr(stricter_unique_ptr<U> &&u):
        std::unique_ptr<T>{std::move(u)}
    {}

private:

    // Don't I forget to hide anything?
    using std::unique_ptr<T>::operator =;
    using std::unique_ptr<T>::release;
    using std::unique_ptr<T>::reset;
    using std::unique_ptr<T>::swap;

};

struct Base {
    virtual ~Base() {}
};

struct Derived: Base {
    Derived(int, float) {}
};

struct Foo {
    Foo(stricter_unique_ptr<Base> base): base{std::move(base)} {
        std::cout << this->base.get() << std::endl;
    }
private:
    std::unique_ptr<Base> base;
};

int main() {
    Foo foo{stricter_unique_ptr<Derived>{42, 3.14}}; // That's it

    stricter_unique_ptr<Derived> p{42, 3.14};
    Foo foo1{std::move(p)}; // This is OK
    Foo foo2{std::move(p)}; // But still possible to cheat
}

Bar按值传递并不是一个真正的选项,因为它太大了。看一下这些评论,我们得出结论,在我这种情况下,简单的转发器是最好的解决方案。事实上,我并不需要多态行为,最终使用了std::forward,就像你一样。 - iFreilicht

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接