有时将 class 指针成员变量初始化为自身的好习惯?

Sometimes a good practice to initialize a class pointer member variable to itself?

对于严格内部的 class,即 而不是 旨在用作 API 的一部分提供给外部客户端,将 class 指针成员变量初始化为自身而不是 NULLnullptr 有什么内在的邪恶吗?

示例请看下面的代码。

#include <iostream>

class Foo
{
public:
  Foo() :
    m_link(this)
  {
  }

  Foo* getLink()
  {
    return m_link;
  }

  void setLink(Foo& rhs)
  {
    m_link = &rhs;
    // Do other things too.
    // Obviously, the name shouldn't be setLink() if the real code is doing multiple things,
    // but this is a code sample.
  }

  void changeState()
  {
    // This is a code sample, but play along and assume there are actual states to change.
    std::cout << "Changing a state." << std::endl;
  }

private:
  Foo* m_link;
};

void doSomething(Foo& foo)
{
  Foo* link = foo.getLink();

  if (link == &foo)
  {
    std::cout << "A is not linked to anything." << std::endl;
  }

  else
  {
    std::cout << "A is linked to something else. Need to change the state on the link." << std::endl;
    link->changeState();
  }
}

int main(int argc, char** argv)
{
  Foo a;
  doSomething(a);

  std::cout << "-------------------" << std::endl;

  // This is a mere code sample.
  // In the real code, I'm fetching B from a container.
  Foo b;
  a.setLink(b);
  doSomething(a);

  return 0;
}

输出

A is not linked to anything.
-------------------
A is linked to something else. Need to change the state on the link.
Changing a state.

优点

将指针变量 Foo::link 初始化为自身的好处是避免意外的 NULL 取消引用。由于指针永远不会为 NULL,那么在最坏的情况下,程序将产生错误输出而不是分段错误。

缺点

但是,此策略的明显缺点是它似乎非常规。大多数程序员习惯于检查 NULL,因此不期望检查与调用指针的对象是否相等。因此,不建议在针对外部消费者的代码库中使用此技术,即希望将此代码库用作库的开发人员。

最后的评论

其他人有什么想法吗?有没有其他人对这个主题发表过任何实质性的意见,尤其是考虑到 C++98?请注意,我使用带有这些标志的 GCC 编译器编译此代码:-std=c++98 -Wall 并且没有发现任何问题。

P.S。请随时编辑此 post 以改进我在此处使用的任何术语。

编辑

Foo 负责它自己的状态。特别是它向用户公开的指针。

作为 public 成员,如果以这种方式公开指针,这是一个非常奇怪的设计决定。我的直觉告诉我,在过去的 30 多年里,像这样的指针不是处理 Foo 状态的负责任的方式。

考虑为该指针提供吸气剂。

Foo* getP() {
    // create a safe pointer for user
    // and indicate an error state. (exceptions might be an alternative)
}

除非您分享 Foo 是什么的更多上下文,否则很难提供建议。

is there anything inherently evil with initializing a class pointer member variable to itself rather than NULL or nullptr?

没有。但正如您所指出的,根据用例的不同,可能会有不同的考虑因素。

我不确定这在大多数情况下是否相关,但在某些情况下对象需要保存其自身类型的指针,因此它确实与这些情况相关。

例如,单向链表中的一个元素将有一个指向下一个元素的指针,因此列表中的最后一个元素通常会有一个 NULL 指针,以表明没有其他元素。所以使用这个例子,结束元素可以指向它自己而不是 NULL 来表示它是最后一个元素。这真的只取决于个人的实施偏好。

很多时候,当您过于努力地使其防崩溃时,您最终可能会不必要地混淆代码。根据具体情况,您可能会掩盖问题并使问题更难调试。例如,回到单链的例子,如果使用指向自身的指针初始化方法,并且程序中的错误试图访问列表中末尾元素的下一个元素,则列表将 return 再次结束元素。这很可能会导致程序永远“遍历”列表。这可能 find/understand 比简单地让程序崩溃并通过调试工具找到罪魁祸首更难。

开始时这不是个好主意,但作为空值取消引用的解决方案却是个可怕的主意。

您没有隐藏空取消引用。曾经。空取消引用是 错误,而不是错误。当错误发生时,程序中的所有不变性都会消失,并且无法保证任何行为。不允许错误立即显现并不能使程序在任何意义上都是正确的,它只会混淆并使调试变得更加困难。


除此之外,一个指向自身的结构是一堆粗糙的蠕虫。考虑你的文案作业

Foo& operator=(const Foo& rhs) {
    if(this != &rhs)
        return *this;
    if(rhs->m_link != &rhs)
        m_link = this;
    else
        m_link = rhs->m_link;
}

你现在每次复制时都必须检查你是否指向你自己,因为它的值可能与它自己的身份相关联。

事实证明,在很多情况下都需要进行此类检查。 swap 应该如何实施?

void swap(Foo& x, Foo& y) noexcept {
    Foo* tx, *ty;
    if(x.m_link == &x)
        tx = &y;
    else
        tx = x.m_link;
    if(y.m_link == &y)
        ty = &x;
    else
        ty = y.m_link;

    x.m_link = ty;
    y.m_link = tx;
}

假设 Foo 有某种 pointer/reference 语义,那么你的相等性现在也是重要的

bool operator==(const Foo& rhs) const {
    return m_link == rhs.m_link || (m_link == this && rhs.m_link == &rhs);
}

不要指着自己。只是不要。