在 C++ 中返回“NULL 引用”?
Posted
技术标签:
【中文标题】在 C++ 中返回“NULL 引用”?【英文标题】:Returning a "NULL reference" in C++? 【发布时间】:2012-05-09 09:49:47 【问题描述】:在 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++ 特定的模式?任何有关正确方法的建议将不胜感激。
【问题讨论】:
“除了使用指针”是否排除智能指针? 返回迭代器是一种经常出现的模式。如果未找到该事物,则返回无效的迭代器。 你的建议isNull()
不是典型的 C++。您要解决的真正问题是什么。如果我们了解您想要做什么,也许我们可以提供更好的想法。
你为什么要有效地问“解决这个问题的方法是什么,除了解决它的正确方法”?为什么你不想使用指针来解决用例指针打算解决的问题?
编辑了您的问题以添加有关该问题的注释,让读者清楚为什么您要使用参考文献进行此操作
【参考方案1】:
在引用期间不能这样做,因为它们永远不应该为 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
:
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:当然,如果不需要增强依赖,推出自己的“可选”类并不太难。 @Troubadour:实际上很难推出自己的可以容纳任何类型的“可选”;您需要嵌入一个具有正确大小和对齐类型的 POD 对象,这一点都不简单。 (当然,如果将其限制为默认可构造类型会容易得多,在这种情况下,它与选项 2 基本相同)。 不错的答案。在 C++11 中,还可以返回 std::unique_ptr为什么“除了使用指针”?使用指针是在 C++ 中的方式。除非您定义一些“可选”类型,它具有您提到的 isNull()
函数之类的东西。 (或使用现有的,例如boost::optional
)
引用的设计和保证是永远不会为空。问“那么我如何使它们为空”是荒谬的。当您需要“可空引用”时,您可以使用指针。
【讨论】:
关于“引用的设计和保证是永远不会为空。”我还没有遇到一个保证引用不为空的实现。该标准不保证引用永远不会为空。这是一项授权,而不是保证。负担在于程序员。不幸的是,进行空引用非常容易(并且高度 UB)。但是+1。如果您想要一个可能为 null 的“引用”,那么要做的是使用指针而不是引用。 @DavidHammen:标准几乎说明了我在上面所做的:引用指向一个对象。这样做时,该标准保证引用指向有效定义良好的 C++ 代码中的对象。当然,您可以欺骗编译器创建“空引用”,但是程序不再受标准指定的行为的约束,并且保证不适用。取决于你的观点,真的。 :) 该标准不保证在展示 UB 的程序中引用是非空的。但它确实保证它们在定义明确的程序中是非空的。 :) @jalf:我们在语义上争论不休,但我仍然喜欢“授权”这个词而不是“保证”。对我来说,“保证”是编译器供应商的负担,而“授权”是程序员的负担。 @jalf:欺骗编译器创建 NULL 引用 (int &n = *static_cast<int*>(NULL);
) 并不难。但是,我绝不会建议这是一个好主意。 :)
@DavidHammen 在某种程度上,你是对的。但实际上有两种看待它的方式。没有什么能阻止程序员尝试定义“空引用”,标准也没有禁止它。它没有强制任何东西。它只是说“如果你这样做,我不会保证你程序的行为”。换句话说,“在任何尊重我的规则的程序中,我保证引用不会为空”。但正如你所说,这是毫无意义的狡辩。【参考方案3】:
一个很好的和相对非侵入性的方法,如果为所有类型实现特殊方法,可以避免这个问题,是与boost.optional 一起使用的方法。它本质上是一个模板包装器,允许您检查所保存的值是否“有效”。
顺便说一句,我认为这在文档中得到了很好的解释,但要注意 boost::optional
或 bool
,这是一个难以解释的结构。
编辑:问题询问“NULL 引用”,但代码 sn-p 有一个按值返回的函数。如果该函数确实返回了引用:
const someResource& getSomething(const std::string& name) const ; // and possibly non-const version
那么该函数只有在被引用的someResource
的生命周期至少与返回引用的对象的生命周期一样长时才有意义(否则您将有一个悬空引用)。在这种情况下,返回一个指针似乎完全没问题:
const someResource* getSomething(const std::string& name) const; // and possibly non-const version
但您必须绝对清楚调用者不拥有指针的所有权,也不应该尝试删除它。
【讨论】:
我很喜欢这种方式,我去看看。 IMO 有点复杂 - 使用 @jalf 所说的指针,不需要额外的库和完美的 c++。 optional 需要值语义,即返回的是一个副本(我过去是通过引用返回的,但不知道是不是ub!) @Nim 我同意,在问题标题的上下文中它可能更简单。问题的主体表明 OP 还需要一种方法来检查返回值的有效性。另外,当返回指针时,必须绝对确定所有权是明确定义的。 boost.optional 是否比使用 unique_ptr 和 shared_ptr 有很多优势?它们都可以测试 nullptr 以查看它们是否无效。 @Benj 确切地说,问题是如何让调用者知道一个值是否有效。【参考方案4】:我可以想出几种方法来处理这个问题:
按照其他人的建议,使用boost::optional
使对象具有表明它无效的状态(Yuk!)
使用指针代替引用
有一个特殊的类实例 空对象
抛出异常以指示失败(并不总是适用)
【讨论】:
对反对票进行解释会很好。特别是对于有多个建议的东西。 :) 我打算建议:有一个类的特殊实例,它是空对象。有点像 C# 使用 String.Empty 的方式。每个类只有一个“null”版本的静态实例。然后您可以将返回的值与“null”实例进行比较。 @Neil 是的,在某些情况下它可能是一种方便的方法。就我个人而言,我不喜欢它:一旦你使用它,你的代码就不能(简单地)假设你手头的对象是有效的。我上面的第二个建议也是如此。 终于有人推荐了一个空对象设计模式。那真的应该是第一位的。 我虽然关于“有一个作为空对象的类的特殊实例”然后遇到了这个。【参考方案5】:与 Java 和 C# 不同,C++ 中的引用对象不能为空。 所以我会建议我在这种情况下使用的 2 种方法。
1 - 使用具有 null 的类型而不是引用,例如 std::shared_ptr
2 - 将引用作为输出参数并返回布尔值表示成功。
bool SomeClass::getSomething(std::string name, SomeResource& outParam)
if (content_.find(name) != content_.end())
outParam = content_[name];
return true;
return false;
【讨论】:
shared_ptr
只能在指针可以比创建它的对象寿命长的情况下使用。
在这种情况下,outParam 不是常量,允许用户为每个人修改它,因此它具有与传递 const 引用不同的功能。【参考方案6】:
下面的这段代码演示了如何返回“无效”引用;它只是使用指针的另一种方式(传统方法)。
不建议您在将被其他人使用的代码中使用它,因为期望返回引用的函数总是返回有效引用。
#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
。现在可以像使用指针一样检查此地址。
【讨论】:
在 Xcode 6.3 上这永远不会起作用:如果 (!&ra),引用的地址将始终评估为 false。【参考方案7】:这里有几个想法:
备选方案 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;
;
【讨论】:
【参考方案8】:还有另一种选择——我不时使用的一种选择,当您真的不希望返回“空”对象而是“空/无效”对象时会这样做:
// 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)
【讨论】:
以上是关于在 C++ 中返回“NULL 引用”?的主要内容,如果未能解决你的问题,请参考以下文章
我用win7系统,无法获取未定义或null引用的属性“options”