在JavaScript或PHP等动态类型语言中,我经常使用以下函数:
function getSomething(name) {
if (content_[name]) return content_[name];
return null; // doesn't exist
}
我返回一个对象(如果存在)或null
如果没有。
使用引用的C ++中的等价物是什么?一般有推荐的模式吗?为此,我看到一些框架具有isNull()
方法:
SomeResource SomeClass::getSomething(std::string name) {
if (content_.find(name) != content_.end()) return content_[name];
SomeResource output; // Create a "null" resource
return output;
}
然后调用者会以这种方式检查资源:
SomeResource r = obj.getSomething("something");
if (!r.isNull()) {
// OK
} else {
// NOT OK
}
然而,必须为每个类实现这种魔术方法似乎很重。当对象的内部状态应该从“null”设置为“not null”时,似乎并不明显。
这种模式还有其他选择吗?我已经知道它可以使用指针完成,但我想知道如何/如果它可以用引用完成。或者我应该放弃在C ++中返回“null”对象并使用一些特定于C ++的模式?任何有关正确方法的建议都将受到赞赏。
答案 0 :(得分:41)
在引用期间不能这样做,因为它们永远不应该为NULL。基本上有三个选项,一个使用指针,另一个使用值语义。
使用指针(注意:这要求在调用者有指针的情况下资源不被破坏;同时确保调用者知道它不需要删除对象):
SomeResource* SomeClass::getSomething(std::string name) {
std::map<std::string, SomeResource>::iterator it = content_.find(name);
if (it != content_.end())
return &(*it);
return NULL;
}
将std::pair
与bool
一起使用以指示该项是否有效(注意:要求SomeResource具有适当的默认构造函数并且构造起来并不昂贵):
std::pair<SomeResource, bool> SomeClass::getSomething(std::string name) {
std::map<std::string, SomeResource>::iterator it = content_.find(name);
if (it != content_.end())
return std::make_pair(*it, true);
return std::make_pair(SomeResource(), false);
}
boost::optional<SomeResource> SomeClass::getSomething(std::string name) {
std::map<std::string, SomeResource>::iterator it = content_.find(name);
if (it != content_.end())
return *it;
return boost::optional<SomeResource>();
}
如果你想要价值语义并且能够使用Boost,我建议选择三。 boost::optional
优于std::pair
的主要优点是,单元化boost::optional
值不构造其封装类型。这意味着它适用于没有默认构造函数的类型,并为具有非平凡构造函数的类型节省时间/内存。
我还修改了你的例子,所以你没有两次搜索地图(通过重用迭代器)。
答案 1 :(得分:24)
为什么“除了使用指针”?使用指针 就像在C ++中一样。除非你定义一些“可选”类型,它类似于你提到的isNull()
函数。 (或使用现有的,如boost::optional
)
参考设计并保证永远不会为空。问“所以如何让它们为空”是荒谬的。当您需要“可以为空的引用”时,可以使用指针。
答案 2 :(得分:6)
一种不错的,相对非侵入性的方法,可以避免在为所有类型实现特殊方法时出现问题,与boost.optional一起使用。它本质上是一个模板包装器,允许您检查保持的值是否“有效”。
BTW我认为这在文档中得到了很好的解释,但要注意boost::optional
的{{1}},这是一个难以解释的结构。
编辑:问题询问“NULL引用”,但代码段有一个按值返回的函数。如果该函数确实返回了引用:
bool
然后该函数只有在被引用的const someResource& getSomething(const std::string& name) const ; // and possibly non-const version
的生命周期至少与返回引用的对象的生命周期一样长时才有意义(否则你将有一个悬空引用)。在这种情况下,返回一个指针似乎很好:
someResource
但你必须让它绝对清楚,调用者不会获得指针的所有权,也不应该尝试删除它。
答案 3 :(得分:5)
我可以想出几种方法来解决这个问题:
boost::optional
答案 4 :(得分:2)
与Java不同,C ++中的C#引用对象不能为空 所以我会建议我在这种情况下使用的两种方法。
1 - 而不是引用使用具有null的类型,例如std :: shared_ptr
2 - 将参考作为out参数获取并返回布尔值以获得成功。
bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
if (content_.find(name) != content_.end())
{
outParam = content_[name];
return true;
}
return false;
}
答案 5 :(得分:1)
以下是一些想法:
备选方案1:
class Nullable
{
private:
bool m_bIsNull;
protected:
Nullable(bool bIsNull) : m_bIsNull(bIsNull) {}
void setNull(bool bIsNull) { m_bIsNull = bIsNull; }
public:
bool isNull();
};
class SomeResource : public Nullable
{
public:
SomeResource() : Nullable(true) {}
SomeResource(...) : Nullable(false) { ... }
...
};
备选方案2:
template<class T>
struct Nullable<T>
{
Nullable(const T& value_) : value(value_), isNull(false) {}
Nullable() : isNull(true) {}
T value;
bool isNull;
};
答案 6 :(得分:1)
下面的代码演示了如何返回&#34;无效&#34;引用;它只是一种使用指针的不同方式(传统方法)。
不建议您在其他人使用的代码中使用它,因为期望返回引用的函数始终返回有效引用。
#include <iostream>
#include <cstddef>
#define Nothing(Type) *(Type*)nullptr
//#define Nothing(Type) *(Type*)0
struct A { int i; };
struct B
{
A a[5];
B() { for (int i=0;i<5;i++) a[i].i=i+1; }
A& GetA(int n)
{
if ((n>=0)&&(n<5)) return a[n];
else return Nothing(A);
}
};
int main()
{
B b;
for (int i=3;i<7;i++)
{
A &ra=b.GetA(i);
if (!&ra) std::cout << i << ": ra=nothing\n";
else std::cout << i << ": ra=" << ra.i << "\n";
}
return 0;
}
宏Nothing(Type)
会返回值,在这种情况下由nullptr
表示 - 您也可以使用0
,参考&#39 ; s地址已设置。现在可以检查此地址,如果您一直在使用指针。
答案 7 :(得分:0)
还有另一种选择-我不时使用的一种选择,当您确实不希望返回“空”对象,而是使用“空/无效”对象时:
// List of things
std::vector<some_struct> list_of_things;
// An emtpy / invalid instance of some_struct
some_struct empty_struct{"invalid"};
const some_struct &get_thing(int index)
{
// If the index is valid then return the ref to the item index'ed
if (index <= list_of_things.size())
{
return list_of_things[index];
}
// Index is out of range, return a reference to the invalid/empty instance
return empty_struct; // doesn't exist
}
它非常简单,并且(取决于另一端在做什么)可以避免在另一侧进行空指针检查的需要。例如,如果您正在生成某些事物列表,例如:
for (const auto &sub_item : get_thing(2).sub_list())
{
// If the returned item from get_thing is the empty one then the sub list will
// be empty - no need to bother with nullptr checks etc... (in this case)
}