【问题标题】:Checking for a null object in C++在 C++ 中检查空对象
【发布时间】:2011-01-07 04:16:46
【问题描述】:

我大部分时间只使用 C,但在 C++ 中遇到了一些不熟悉的问题。

假设我在 C 中有一些这样的函数,这将是非常典型的:

int some_c_function(const char* var)
{
    if (var == NULL) {
        /* Exit early so we don't dereference a null pointer */
    }
    /* The rest of the code */
}

假设我正在尝试用 C++ 编写一个类似的函数:

int some_cpp_function(const some_object& str)
{
    if (str == NULL)  // This doesn't compile, probably because some_object doesn't overload the == operator

    if (&str == NULL) // This compiles, but it doesn't work, and does this even mean anything?
}

基本上,我要做的只是防止在使用 NULL 调用 some_cpp_function() 时程序崩溃。

  • 使用 C++ 对象执行此操作的最典型/最常见的方法是什么(不涉及重载 == 运算符)?

  • 这是正确的方法吗?也就是说,我不应该编写将对象作为参数的函数,而是编写成员函数吗? (但即便如此,请回答原问题)

  • 在采用对象引用的函数或采用 C 风格指针指向对象的函数之间,是否有理由选择一个而不是另一个?

【问题讨论】:

  • 这是个好问题。很遗憾,大多数回答的人都不明白你在问什么。我认为 Nilesh 提供了正确的答案;而且我相当肯定它即使在跨越应用程序/共享对象边界时也能工作如果共享对象导出空对象。

标签: c++ object null


【解决方案1】:

基本上,我要做的就是 防止程序崩溃时 some_cpp_function() 被调用 空。

无法使用 NULL 调用函数。拥有引用的目的之一是,它将始终指向某个对象,因为您必须在定义它时对其进行初始化。不要将引用视为花哨的指针,将其视为对象本身的别名。那么就不会出现这种混乱了。

【讨论】:

  • 这几乎不是谁;e(甚至是主要)的目的。
  • 实际上可以调用some_cpp_function(NULL),如果some_object有一个非显式构造函数,它接受一个指针(或任何可以从0隐式转换的东西)。例如,如果some_objectstd::string 的类型定义,程序将在临时std::string 对象的构造函数中崩溃,但这发生在some_cpp_function 之外。
  • 可以通过取消引用 NULL 指针来创建“NULL 引用”(这会导致行为本身未定义)。我想这在实践中可能会发生,但这是代码中的一个严重错误,应该更彻底地处理,例如使用断言。
  • 可以在引用中传递一个空对象。例如
  • 我认为您错过了问题的重点。不再关注第一个代码块和NULL 字符指针,而是循环回到标题:在 C++ 中检查空对象 和第二个代码块。也就是说,如何表示一个众所周知的 nil 对象用作可选参数。他可以使用空对象的可区分值调用函数。
【解决方案2】:

引用不能为 NULL。该接口使您可以将真实对象传递给函数。

所以没有必要测试 NULL。这也是 C++ 中引入引用的原因之一。

请注意,您仍然可以编写一个接受指针的函数。在这种情况下,您仍然需要测试 NULL。如果该值为 NULL,那么您就像在 C 中一样提前返回。注意:当指针为 NULL 时,您不应该使用异常。如果参数永远不应为 NULL,那么您将创建一个使用引用的接口。

【讨论】:

  • +1,尤其是在指针为 NULL 时不会引发异常。
  • 有时接口采用非空指针而不是引用是合适的。例如,如果接口将获得所指向对象的所有权。
  • 或者在处理C字符串的时候,比如std::string在各个地方都需要非空指针。
  • 杂乱无章 - 我个人不同意,我不相信在这种情况下采用指针会给你带来任何好处,除非错误地向调用者指示该参数是可选的,并且当它不是并且不能时可能为 null .恕我直言,所有权转移语义和可选性是正交的。
  • Omnifarious:如果要指示指针的所有权转移,更合适的参数是 std::auto_ptr。这明确表明该函数正在获取参数的所有权。
【解决方案3】:

C++ 引用既不是指针也不是 Java/C# 样式的引用,并且不能为 NULL。它们的行为就好像它们是另一个现有对象的别名。

在某些情况下,如果您的代码中存在错误,您可能会获得对已经死亡或不存在的对象的引用,但您能做的最好的事情是希望程序尽快终止以便能够调试发生了什么以及您的程序损坏的原因。

也就是说,我已经看到代码检查“空引用”的操作类似于:if ( &reference == 0 ),但标准很清楚,在格式良好的程序中不能有空引用。如果引用绑定到空对象,则程序格式错误,应予以纠正。如果您需要可选值,请使用指针(或诸如boost::optional 之类的更高级别的构造),而不是引用。

【讨论】:

  • +1 是唯一提到(损坏)对已删除堆对象等的引用的答案。
【解决方案4】:

正如大家所说,引用不能为空。那是因为,引用指的是一个对象。在您的代码中:

// this compiles, but doesn't work, and does this even mean anything?
if (&str == NULL)

您正在获取对象str 的地址。根据定义,str 存在,所以它有一个地址。所以,它不可能是NULL。因此,从句法上讲,上述内容是正确的,但从逻辑上讲,if 条件总是为假。

关于您的问题:这取决于您想做什么。您希望函数能够修改参数吗?如果是,请传递参考。如果没有,不要(或传递对const 的引用)。有关详细信息,请参阅this C++ FAQ

通常,在 C++ 中,大多数人更喜欢通过引用传递而不是传递指针。原因之一正是您发现的:引用不能是NULL,从而避免了在函数中检查它的麻烦。

【讨论】:

  • 您会在某些地方读到,您可以通过以下方式获得空引用:type &null = *(type*)0;,但根据标准取消引用空指针是不正确的,明确指出不能有空引用在一个结构良好的程序中。
  • @dribeas:您可能无意中输入了&null = *pointertotype;,而pointertotype 可能最终变成了NULL。但除非你使用指针,否则你是完全安全的。
  • @dribas:从技术上讲,上面是 NOT 取消引用 NULL。一元 * 运算符(有时被错误地称为取消引用运算符)返回 一个左值,该左值引用表达式指向的对象或函数 这里的关键字是“引用”。没有提到要取消引用的地址。请参阅标准的第 5.3.1/1 节。但我同意一个格式良好的程序不能有 NULL 引用,任何做上述操作的人肯定应该在应用一元 * 运算符之前检查指针是否为 NULL。
  • 问题是取消引用空指针是标准中的“未定义行为”。再一次,在这一点上,可能发生的最好的事情是应用程序因无效的内存访问而死亡,以便您可以尝试调试,针对“空引用”进行测试没有任何意义,因为这不可能发生 -形成的程序。问题是,在大多数人看来,引用是指针,因此认为那里的赋值实际上并没有取消引用指针。我并不是说你不能得到那种行为,但是当它发生时它就是一个错误。
  • @Martin:该标准明确认为:[dcl.ref]/4:“注意:特别是,空引用不能存在于定义良好的程序中,因为创建这样的唯一方法引用会将其绑定到通过取消引用空指针获得的“对象”,这会导致未定义的行为”
【解决方案5】:

在引用的情况下,可以使用一个特殊的指定对象作为空对象,如下:

class SomeClass {
    public:
        int operator==(SomeClass &object) {
            return (this == &object);
        }

    static SomeClass NullObject;
};

SomeClass SomeClass::NullObject;

void print(SomeClass &val) {
    if(val == SomeClass::NullObject)
        printf("\nNULL");
    else
        printf("\nNOT NULL");
}

【讨论】:

  • 您可以这样做,但这并不是最清晰的方法。如果可以只使用指针,为什么要发明空对象?空指针意味着它不指向任何常规对象。
【解决方案6】:

您应该只对指针使用 NULL。您的函数接受引用,并且它们不能为 NULL。

像在 C 中编写函数一样编写函数。

【讨论】:

  • 我认为您错过了问题的重点。不再关注第一个代码块和NULL 字符指针,而是循环回到标题:在 C++ 中检查空对象 和第二个代码块。也就是说,如何表示一个众所周知的 nil 对象用作可选参数。
  • @jww 我不明白你。我说过引用不能为空,并使用他的原始函数,传递一个指向 some_type 的指针,如果他希望它是可选的。
【解决方案7】:

C++ 引用自然不能为空,你不需要检查。该函数只能通过传递对现有对象的引用来调用。

【讨论】:

  • 我认为您错过了问题的重点。不再关注第一个代码块和NULL 字符指针,而是循环回到标题:在 C++ 中检查空对象 和第二个代码块。也就是说,如何表示一个众所周知的 nil 对象用作可选参数。
  • @jww 我的答案正是关于第二个代码块。
【解决方案8】:
  • 使用 C++ 对象(不涉及重载 == 运算符)最典型/最常见的方法是什么?
  • 这是正确的方法吗? IE。我不应该编写将对象作为参数的函数,而是编写成员函数吗? (但即使是这样,请回答原来的问题。)

不,引用不能为空(除非未定义行为已经发生,在这种情况下,所有赌注都已经关闭)。是否应该编写方法或非方法取决于其他因素。

  • 在采用对象引用的函数或采用 C 样式指针指向对象的函数之间,是否有理由选择其中一个?

如果需要表示“无对象”,则传递一个指向函数的指针,并让该指针为NULL:

int silly_sum(int const* pa=0, int const* pb=0, int const* pc=0) {
  /* Take up to three ints and return the sum of any supplied values.

  Pass null pointers for "not supplied".

  This is NOT an example of good code.
  */
  if (!pa && (pb || pc)) return silly_sum(pb, pc);
  if (!pb && pc) return silly_sum(pa, pc);
  if (pc) return silly_sum(pa, pb) + *pc;
  if (pa && pb) return *pa + *pb;
  if (pa) return *pa;
  if (pb) return *pb;
  return 0;
}

int main() {
  int a = 1, b = 2, c = 3;
  cout << silly_sum(&a, &b, &c) << '\n';
  cout << silly_sum(&a, &b) << '\n';
  cout << silly_sum(&a) << '\n';
  cout << silly_sum(0, &b, &c) << '\n';
  cout << silly_sum(&a, 0, &c) << '\n';
  cout << silly_sum(0, 0, &c) << '\n';
  return 0;
}

如果永远不需要表示“无对象”,那么引用就可以正常工作。事实上,运算符重载要简单得多,因为它们采用重载。

你可以使用类似 boost::optional 的东西。

【讨论】:

  • 还要注意使用if (p)if (!p)而不是!= NULL== NULL
猜你喜欢
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-05
  • 2020-11-12
  • 1970-01-01
  • 2022-08-17
  • 1970-01-01
相关资源
最近更新 更多