在我们的项目中发现一个神秘问题后,我仍然感到震惊。
我们意识到调用 HasMember("string") 正在执行额外的查找。因此,出于性能原因,我们对其进行了更改。
主要思想是:
而不是调用 HasMember 并随后预先缓存引用,例如:
rapidjson::Document d;
d.Parse<0>(json);
if(d.HasMember("foo"))
{
const rapidjson::Value& fooValue = d["foo"];
// do something with fooValue
}
变成:
rapidjson::Document d;
d.Parse<0>(json);
const rapidjson::Value& fooValue = d["foo"];
if( !fooValue.IsNull() )
{
// do something with fooValue
}
这非常好,我们节省了执行两次查找而不是一次查找。然而,问题来了。
如果您开始查看 rapidjson 如何实现 nullvalue(在 seek 失败时默认返回),您将看到以下代码:
//! Get the value associated with the object's name.
GenericValue & operator[](const Ch* name) {
// Check
if (Member * member = FindMember(name)) {
return member->value;
} else {
// Nothing
static GenericValue NullValue;
return NullValue;
}
}
// Finder
const GenericValue & operator[] (const Ch* name) const {
// Return
return const_cast<GenericValue &> (* this)[name];
}
所以,如果没有找到我们返回一个局部静态变量的成员。乍一看这可能听起来足够好,但由于这是通过引用返回的,因此很容易导致隐藏的错误。
想象一下,有人更改了静态 NullValue 的引用。这将导致对 IsNull 的所有进一步调用(在查找它之后)将失败,因为 NullValue 更改为另一种类型,甚至更改为随机内存。
那么,你在乎什么呢?你认为这是一个很好的空模式示例吗?
我很困惑,我喜欢返回默认空值的想法,但由于不作为 const 返回,这很危险。而且,即使我们在所有情况下都将它返回为 const,开发人员仍然可以使用 const_cast(但我不认为,如果他们这样做,将由他们负责)。
我想听听像这样的其他案例和例子。如果有人能在 rapidjson 代码下给出一个真正的解决方案,那基本上就很棒了。