【发布时间】:2015-04-05 18:20:51
【问题描述】:
详情
我在此处 (https://softwareengineering.stackexchange.com/questions/152094/null-pointers-vs-null-object-pattern) 和此处 (http://en.wikipedia.org/wiki/Null_Object_pattern#C.2B.2B) 找到了有关 空对象模式 的一些信息。
但是,C++ 实现并没有说明我的用例。
我还看到了 Nullable Type (http://en.wikipedia.org/wiki/Nullable_type) 的相关链接。
用例
我有一个不属于层次结构的对象,通常不会在堆上分配。此外,没有一个方便的值可以用作标记来指示 null。希望下面的代码可以清楚地说明用例。
class ContrivedType
{
public:
ContrivedType() :
mValue(0)
{
// Do nothing
}
bool operator==(const ContrivedType& other) const
{
return mValue == other.mValue;
}
void setValue(std::uint16_t value)
{
mValue = value;
}
private:
// All values in the range [0, 65535] are valid for use
std::uint16_t mValue;
};
class Foo
{
public:
const ContrivedType getValue() const
{
return mValue;
}
void setValue(const ContrivedType &value)
{
mValue = value;
}
private:
ContrivedType mValue;
};
int main()
{
Foo f;
if (f.getValue() == ContrivedType())
{
// Ambiguous case
// - Was this value explicitly set to be the same value
// as when it's default constructed
// OR
// - Was the value never set
}
return 0;
}
可能的解决方案 1
强制需要区分默认状态和未设置的ContrivedType用户使用指针并动态分配ContrivedType。也许是这样的?
class Foo
{
public:
Foo() :
mValue(nullptr)
{
// Do nothing
}
const ContrivedType* getValue() const
{
return mValue.get();
}
void setValue(const ContrivedType &value)
{
if (!mValue)
{
mValue.reset(new ContrivedType(value));
}
else
{
*mValue = value;
}
}
private:
std::unique_ptr<ContrivedType> mValue;
};
现在很清楚ContrivedType 是否已设置。
可能的解决方案 2
更新ContrivedType的实现以支持null的概念。
class ContrivedType
{
public:
ContrivedType() :
mState(nullptr)
{
// Do nothing
}
explicit ContrivedType(std::uint16_t value) :
mState(&mStorage)
{
mStorage.mValue = value;
}
bool isNull() const
{
return mState == nullptr;
}
bool operator==(const ContrivedType& other) const
{
if (!isNull())
{
return mStorage.mValue == other.mStorage.mValue;
}
else
{
return other.isNull();
}
}
void setValue(std::uint16_t value)
{
mStorage.mValue = value;
if (!mState)
{
mState = &mStorage;
}
}
private:
struct State
{
// All values in the range [0, 65535] are valid for use
std::uint16_t mValue;
};
State mStorage;
// This will point to the storage when a value actually set
State* mState;
};
问题
这个概念是否有既定的模式或习语?如果没有,有什么实施建议吗?
基本原理
在实际代码中,有一个或多个成员的类在某些情况下是可选的。这些类正在使用支持缺失字段(即可选字段)的协议通过套接字进行序列化。序列化可以跳过 optional 字段,而不是浪费字节序列化未明确设置的默认构造对象。例如,updateFoo(const Foo&) 函数。如果仅更新现有Foo 实例的子集,则只需序列化这些字段。
编辑
看起来std::experimental::optional(@myaut 提请我注意)是我想要使用的,但我无权访问它。
现在我需要使用适用于 Visual Studio 2013(2015 可能还可以)和 g++ 4.8 的解决方案。
【问题讨论】:
-
或许你应该看看
std::optional -
@myaut 谢谢,我不熟悉这个,但我会研究一下。
-
我认为您看不到在 C++ 中实现的这种模式是因为不需要它。在 Java 和 C# 中,将 null 作为无效结果返回是很常见的。由于 Java/C# 中的所有内容都在堆中分配(甚至原语也自动包装为对象),因此每个函数都可以返回 null。在 C++ 中,不可能在不返回指针的函数上返回 null。您必须抛出异常或以其他方式处理无效,而不是返回 null。这是 C++ 开发人员不返回 null 对象的方式——仅仅因为 C++ 通过其他方式处理无效。
-
@DavidHaim 我并不是想通过提出以下问题来断言你错了,我只是好奇。如果 C++ 中不需要
std::optional有什么意义?我肯定想知道区别所以我应该在 C++ 中处理这个吗?您是否建议选项 1? -
@DavidHaim:实际上即使在 Java 中这也可能被认为是有害的(即 Robert Martin 在他的“清洁代码”一书中反对这一点)。
标签: c++