在我的代码中,我注意到我经常需要检查nullptr,即使nullptr不可能(根据指定的要求)。
然而,nullptr可能仍然会发生,因为其他人可能会发送nullptr认为这是可以的(不幸的是并非所有人都读/写规范),并且除非在测试期间在运行时触发问题(并且高),否则无法捕获此缺陷测试覆盖率很高)。因此,它可能会导致客户报告许多发布后的错误。
e.g。
class data
{
virtual void foo() = 0;
};
class data_a : public data
{
public:
virtual void foo(){}
};
class data_b : public data
{
public:
virtual void foo(){}
};
void foo(const std::shared_ptr<data>& data)
{
if(data == nullptr) // good idea to check before use, performance and forgetting check might be a problem?
return;
data->foo();
}
通常我会简单地使用值类型并通过引用传递和复制。但是,在某些情况下,我需要需要指针或引用的多态性。
所以我开始使用以下“编译时多态”。
class data_a
{
public:
void foo(){}
private:
struct implementation;
std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy
};
class data_b
{
public:
void foo(){}
private:
struct implementation;
std::shared_ptr<implementation> impl_; // pimpl-idiom, cheap shallow copy
};
class data
{
public:
data(const data_a& x) : data_(x){} // implicit conversion
data(const data_b& x) : data_(x){} // implicit conversion
void foo()
{
boost::apply(foo_visitor(), data_);
}
private:
struct foo_visitor : public boost::static_visitor<void>
{
template<typename T>
void operator()(T& x){ x.foo(); }
};
boost::variant<data_a, data_b> data_;
}
void foo(const data& data)
{
data.foo();
}
在实际可行时,还有其他人认为这是个好主意吗?或者我错过了什么?这种做法有任何潜在的问题吗?
编辑:
使用引用的“问题”是您无法移动引用的所有权(例如,返回对象)。
data& create_data() { data_a temp; return temp; } // ouch... cannot return temp;
rvalue引用的问题(多态与rvalue引用有效吗?)然后就变成了你不能共享所有权。
data&& create_data() { return std::move(my_data_); } // bye bye data
基于shared_ptr的“安全”指针确实听起来不错,但我仍然想要一个解决方案,在编译时强制执行非null,可能无法实现。
答案 0 :(得分:4)
您始终可以使用null object pattern和参考。你不能传递(好吧你可以,但这是用户的错误)一个空引用。
答案 1 :(得分:3)
我个人更喜欢在类型中编码null可能性,因此使用boost::optional
。
data_holder
类,它始终拥有data
(但允许多态)data_holder
(非空)或boost::optional<data_holdder>
这样很清楚它是否可以为空。
现在,困难的部分是让data_holder
永远不要保留空指针。如果使用data_holder(data*)
形式的构造函数定义它,那么构造函数可能会抛出。
另一方面,它可以简单地采用一些参数,并将实际构造推迟到Factory(使用Virtual Constructor Idiom)。你仍然检查工厂的结果(并在必要时抛出),但你只有一个地方可以检查(工厂),而不是每一个施工点。
您也可以检查boost::make_shared
,以查看参与转发的操作。如果你有C ++ 0x,那么你可以有效地参数转发并得到:
template <typename Derived>
data_holder(): impl(new Derived()) {}
// Other constructors for forwarding
不要忘记将默认构造函数(非模板)声明为私有(而不是定义它)以避免意外调用。
答案 2 :(得分:2)
non-null pointer
是一个众所周知的概念,例如用于C的安全子集。是的,这可能是有利的。
而且,您应该使用智能指针。根据您的使用情况,您可能希望从类似于boost::shared_ptr
或tr1::unique_ptr
的内容开始。但是,assert
,operator*
和operator->
上只有get()
非空的内容,而是抛出异常。
ETA:忘了这一点。虽然我认为这种通用方法很有用(没有未定义的行为等等),但它并没有为您提供非空的编译时检查,这可能是您想要的。为此,您将在整个代码中普遍使用此类非空指针,如果没有语言支持,这仍然会泄漏。
答案 3 :(得分:1)
通常,当您使用上面概述的虚函数时,这是因为您不想知道实现所需接口的所有类。在您的示例代码中,您将枚举实现(概念)接口的所有类。当您必须随着时间的推移添加或删除实现时,这会变得令人沮丧。
您的方法也会干扰管理依赖项,因为您的类数据依赖于所有类,而使用多态性可以更容易地限制对特定类的依赖性。使用pimpl习语减轻了这一点,但我一直认为pimpl习语很烦人(因为你有两个类必须保持同步才能代表一个概念)。
使用引用或检查智能指针似乎是一种更简单,更清晰的解决方案。其他人已经在评论这些,所以我现在不会详细说明。