简体   繁体   English

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

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

For a strictly internal class that is not intended to be used as part of an API provided to an external client, is there anything inherently evil with initializing a class pointer member variable to itself rather than NULL or nullptr ? For a strictly internal class that is not intended to be used as part of an API provided to an external client, is there anything inherently evil with initializing a class pointer member variable to itself rather than NULL or nullptr ?

Please see the below code for an example.请参阅下面的代码以获取示例。

#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;
}

Output Output

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

Pros优点

The benefit to initializing the pointer variable, Foo::link , to itself is to avoid accidental NULL dereferences.将指针变量Foo::link初始化为自身的好处是避免意外的 NULL 取消引用。 Since the pointer can never be NULL, then at worst, the program will produce erroneous output rather than segmentation fault.由于指针永远不可能是 NULL,那么在最坏的情况下,程序将产生错误的 output 而不是分段错误。

Cons缺点

However, the clear downside to this strategy is that it appears to be unconventional.然而,这种策略的明显缺点是它似乎是非常规的。 Most programmers are used to checking for NULL, and thus don't expect to check for equality with the object invoking the pointer.大多数程序员习惯于检查 NULL,因此不要指望检查与调用指针的 object 是否相等。 As such, this technique would be ill-advised to use in a codebase that is targeted for external consumers, that is, developers expecting to use this codebase as a library.因此,不建议将此技术用于针对外部消费者的代码库,即希望将此代码库用作库的开发人员。

Final Remarks最后的话

Any thoughts from anyone else?其他人有什么想法吗? Has anyone else said anything substantial on this subject, especially with C++98 in consideration?有没有其他人在这个主题上发表过任何实质性的言论,尤其是考虑到 C++98 ? Note that I compiled this code with a GCC compiler with these flags: -std=c++98 -Wall and did not notice any issues.请注意,我使用带有以下标志的 GCC 编译器编译了此代码: -std=c++98 -Wall并且没有发现任何问题。

PS Please feel free to edit this post to improve any terminology I used here. PS 请随时编辑这篇文章以改进我在这里使用的任何术语。

Edits编辑

  • This question is asked in the spirit of other good practice questions, such as this question about deleting references .这个问题是本着其他良好实践问题的精神提出的,例如关于删除引用的问题。
  • A more extensive code example has been provided to clear up confusion.提供了更广泛的代码示例以消除混淆。 To be specific, the sample is now 63 lines which is an increase from the initial 30 lines.具体来说,样本现在是 63 行,比最初的 30 行有所增加。 Thus, the variable names have been changed and therefore comments referencing Foo:p should apply to Foo:link .因此,变量名称已更改,因此引用Foo:p的注释应适用于Foo:link

is there anything inherently evil with initializing a class pointer member variable to itself rather than NULL or nullptr ?将 class 指针成员变量初始化为自身而不是NULLnullptr有什么固有的邪恶吗?

No. But as you pointed out, there might be different considerations depending on the use case.不,但是正如您所指出的,根据用例的不同,可能会有不同的考虑因素。

I'm not sure this would be relevant under most circumstances, but there are some instances where an object needs to hold a pointer of its own type, so its really just pertinent to those cases.我不确定这在大多数情况下是否相关,但在某些情况下 object 需要保存自己类型的指针,因此它确实与这些情况有关。

For instance, an element in a singly-linked list will have a pointer to the next element, so the last element in the list would normally have a NULL pointer to show there are no further elements.例如,单链表中的一个元素将有一个指向下一个元素的指针,因此列表中的最后一个元素通常会有一个 NULL 指针,以表明没有其他元素。 So using this example, the end element could instead point to itself instead of NULL to denote it is the last element.因此,使用此示例,结束元素可以改为指向自身而不是 NULL 以表示它是最后一个元素。 It really just depends on personal implementation preference.这真的取决于个人的实施偏好。

Many times, you can end up obfuscating code needlessly when trying too hard to make it crash-proof.很多时候,当你太努力使其防崩溃时,最终可能会不必要地混淆代码。 Depending on the situation, you might mask issues and make problems much harder to debug.根据具体情况,您可能会掩盖问题并使问题更难调试。 For instance, going back to the singly-linked example, if the pointer-to-self initialization method is used, and a bug in the program attempts to access the next element from the end element in the list, the list will return the end element again.例如,回到单链接的例子,如果使用了指向自身的初始化方法,并且程序中的一个错误试图从列表中的末尾元素访问下一个元素,则列表将返回末尾再次元素。 This would most likely cause the program to continue "traversing" the list for eternity.这很可能会导致程序永远“遍历”列表。 That might be harder to find/understand than simply letting the program crash and finding the culprit via debugging tools.这可能比简单地让程序崩溃并通过调试工具找到罪魁祸首更难找到/理解。

Foo is responsible for its own state. Foo 负责自己的 state。 Especially pointers it exposes to its users.特别是它向用户公开的指针。

If you expose a pointer in this fashion, as a public member, it is a very odd design decision.如果您以这种方式公开指针,作为公共成员,这是一个非常奇怪的设计决定。 My gut has told me the last 30 odd years a pointer like this is not a responsible way to handle Foo's state.我的直觉告诉我,在过去的 30 多年里,这样的指针不是处理 Foo 的 state 的负责任的方式。

Consider providing getters for this pointer instead.请考虑为此指针提供 getter。

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

Unless you share more context what Foo is, advice is hard to provide.除非您分享更多关于 Foo 的上下文,否则很难提供建议。

It's a bad idea to start with, but a horrendous idea as a solution to null dereferences.一开始是个坏主意,但作为 null 取消引用的解决方案,这是一个可怕的想法。

You don't hide null dereferences.您不会隐藏 null 取消引用。 Ever.曾经。 Null dereferences are bugs , not errors. Null 取消引用是错误,而不是错误。 When bugs happens, all invariances in your program goes down the toilet and there can be no guarantee for any behaviour.当错误发生时,程序中的所有不变性都会消失,并且无法保证任何行为。 Not allowing a bug to manifest itself immediately doesn't make the program correct in any sense, it only serves to obfuscate and make debugging significantly more difficult.不允许错误立即显现出来并不能使程序在任何意义上都是正确的,它只会造成混淆并使调试变得更加困难。


That aside, a structure pointing into itself is a gnarly can of worms.除此之外,一个指向自身的结构是一个粗糙的蠕虫罐头。 Consider your copy assignment考虑你的副本分配

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

You now have to check whether you're pointing to yourself every time you copy because its value is possibly tied to its own identity.您现在必须检查每次复制时是否指向自己,因为它的价值可能与它自己的身份相关。

As it turns out, there's plenty of cases where such checks are required.事实证明,在很多情况下都需要进行此类检查。 How is swap supposed to be implemented? 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;
}

Suppose Foo has some sort of pointer/reference semantics, then your equality is now also non-trivial假设Foo具有某种指针/引用语义,那么您的相等性现在也很重要

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

Don't point into yourself.不要指向自己。 Just don't.只是不要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM