【问题标题】:Is it legal C++ to test the this-pointer in a member function?在成员函数中测试 this 指针是否合法 C++?
【发布时间】:2013-01-08 14:58:03
【问题描述】:

我有一个涉及不同类类型对象的应用程序。对象由指针引用。空指针表示关联对象不存在。目前调用代码比较繁琐,因为每次使用指向对象的指针时,都会测试指针值是否为空,并采取适当的措施使其为空。因为在不存在的情况下要采取的默认操作取决于对象的类型,所以我更愿意在对象本身的类中而不是在调用程序中对其进行编码。这会产生如下结构:

class C
{ ... 
  void member_func() //non-virtual !
  { if (this) { do something with the object ... }
    else { take some default action }
  }
  ...
};

显然成员函数不能是虚函数,因为当对象不存在时查找表也不存在,虚调用就会失败。但是这段代码对于非虚拟成员函数来说是合法的 C++ 吗?对于我尝试过的编译器,它似乎可以正常工作,但我担心可能的不可移植性。在标准中,我找不到明确允许或明确禁止此类结构的条款。

【问题讨论】:

  • 合法吗?当然……定义清楚?不太确定。
  • @StoryTeller:在空指针上调用成员函数是未定义的行为,所以答案是定义不明确。

标签: c++ null this


【解决方案1】:

this 在成员函数中永远不会为空,因此您执行的检查是无用的。

正如 Matthieu M. 在评论中指出的那样,如果您在代码中执行此类操作:

C* c = 0; 
c->member();

这会导致未定义的行为,这是不好的

【讨论】:

  • 或者更确切地说,如果您执行X* x = 0; x->member();,那么您调用 Undefined Behavior 并且所有赌注都关闭了。
  • @MatthieuM。确切地。感谢您的澄清!介意我将此添加到我的答案中吗?
  • 我很确定未定义的行为包括this == NULL 等观察到的结果......所以它可以发生。我们都同意你不应该这样做,但你的第一句话就是错的。
  • @Useless:关键是在一个行为未定义的程序中,仅仅因为this == NULL 的计算结果为真,@ 987654326@“是”为空。在那条推论上,您可能会编写一些代码(在某些实现上)使值看起来“是”两个不同的东西。没有什么是真的,一切都是允许的,就像一个穿着滑稽斗篷的家伙曾经告诉我的那样。 在没有 UB 的程序中this 不为空,不用说,关于程序行为的全面陈述仅适用于行为已定义的程序。
【解决方案2】:

正如已经指出的,this 永远不能是空指针。如果是,您已经调用了未定义的行为。相反,您可以创建一组重载函数,如下所示:

void DoTheThing(C* cp)
{
    if (cp)
        cp->member_func();
    else
    {
        // take some default action
    }
}

void DoTheThing(B* bp)
{
    if (bp)
        bp->some_other_member_func();
    else
    {
        // take some default action
    }
}

如果您要调用的函数在每个类中具有相同的名称,那么您可以在每个类中创建一个静态函数,该函数执行该类的默认操作(都具有相同的名称),并制作一个模板:

template<typname T>
void DoTheThing(T* tp)
{
    if (tp)
        tp->member_func();
    else
        T::default_action()
}

【讨论】:

  • 为什么this 永远不能为空?我认为在 C++ 中,this 是每个成员函数的隐式第一个参数。使用空指针作为参数调用函数是合法的。 this 仅在访问数据成员时被取消引用:此时,如果 this 为空,则行为将变得未定义。仅仅调用一个非虚拟成员函数不应该涉及取消引用this, oder ?
  • @user1958486:尽管您的论点听起来很合理,但它只是未定义的行为。也许不应该,但确实如此。给我一分钟,我会得到标准参考。
  • @user1958486: [5.2.5/2] 表达式 E1->E2 被转换为等价形式 (*(E1)).E2 -- 所以,它确实涉及空指针的取消引用。
  • 这是未定义的行为,因为标准没有规定函数调用机制。例如,假设不可能通过空指针进行虚拟调用是非常合理的。对于非虚拟呼叫,我认为没有任何实际原因需要 是 UB,而不是使用 this 进行呼叫空指针。但是就像 Benjamin 说它只是 UB,标准说你不能取消引用空指针。 “this是一个隐藏的第一个参数”是一个类比来解释被调用的代码如何知道它被调用了什么对象,它不是this的定义。
【解决方案3】:

按照标准,该代码是不合法的,在实践中使用(即不好的做法)。

事实上,IIRC MFC 在内部使用这些检查。

【讨论】:

  • 你能指出标准的相关部分吗?我的印象是它只是未定义。
  • @StoryTeller 非法,是的,
  • MFC 可以使用非标准代码,因为它带有编译器。 (如果你在微软工作,然后给隔壁房间的那个人买了杯啤酒,那它就突然被实现定义了)。
【解决方案4】:

检查this == NULL 是否没有问题。 调用方法是通过一个NULL对象指针。

如果你想把检查保存在某个地方,你可以把它放在一个智能指针类中,如果持有的指针为空,它可以采取适当的行动。如果“适当的操作”是由所持有的类型唯一确定的,则可以使用特征类来指定它。

这样,您的 NULL 检查和它们的逻辑保持在一起,而不是混入调用者或方法代码中。


// specialize this to provide behaviour per held type
template <typename T> struct MaybeNullDefaultAction {
    void null_call() { throw std::runtime_error("call through NULL pointer"); }
}

template <typename T> class MaybeNull: MaybeNullDefaultAction<T> {
    T *ptr;
public:
    explicit MaybeNull(T *p) : ptr(p) {}

    T* operator-> () {
        if (!ptr)
            null_call();
        // null_call should throw to avoid returning NULL here
        return ptr;
    }
};

不幸的是,我看不到不投掷的方法。无法拦截所有方法名称的函数调用,否则我只会从 operator-&gt; 返回 *this 并在 operator() 中完成工作。

【讨论】:

  • 就是我想说的:)
  • 不幸的是,对空对象采取的适当操作通常不仅取决于对象的类型,还取决于调用的特定成员函数。一个解决方案可能是不让任何指针保持为空,而是在所需对象不存在时创建给定类的本地“空”实例。在某些应用程序中,对象不存在并不是错误,而是一种常见的情况,在这种情况下,涉及throw 的解决方案并不总是合适的。
  • @user1958486:听起来你需要重新考虑你的方法。为什么你有这么多无用的空实例?也许可以进一步总结。
【解决方案5】:

我认为不允许这样做。您要求参考标准。我相信首先感兴趣的是 9.3.1 非静态成员函数,1.:

可以为其类的对象调用非静态成员函数 类型,或从其类派生的类的对象(第 10 条) 类型,使用类成员访问语法 (5.2.5, 13.3.1.1)。

接下来我们看5.2.5 类成员访问,2.:

表达式 E1->E2 被转换为等价形式 (*(E1)).E2; 5.2.5 的其余部分将仅处理第一个选项(点)。

因此,如果 E1 是 nullptr,则 *E1 将不被允许。所以至少是我的猜测。

【讨论】:

  • 谢谢。当 E1 为空时,这条规则似乎确实不允许表达式 E1->E2。可以想象,编译器可以以这样一种方式实现成员函数,当使用空 this 指针调用它们时,它们的行为不会像预期的那样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
  • 1970-01-01
  • 2012-02-29
  • 2014-10-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多