有时,将类指针成员变量初始化为其自身是一种很好的做法



对于一个严格内部类,该类是而非,旨在用作提供给外部客户端的API的一部分,将类指针成员变量初始化为自身而不是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,那么在最坏的情况下,程序将产生错误的输出,而不是分段错误。

Cons

然而,这种策略的明显缺点是,它似乎是非常规的。大多数程序员习惯于检查NULL,因此不希望通过调用指针的对象来检查相等性。因此,在面向外部消费者的代码库中使用此技术是不明智的,也就是说,开发人员希望将此代码库用作库。

最后备注

其他人有什么想法吗?有没有其他人在这个问题上说了什么实质性的话,尤其是在考虑C++98的情况下?请注意,我使用带有以下标志的GCC编译器编译了这段代码:-std=c++98 -Wall,没有注意到任何问题。

附言:请随意编辑这篇文章,以改进我在这里使用的任何术语。

编辑

  • 这个问题是本着其他良好实践问题的精神提出的,比如这个关于删除参考文献的问题
  • 提供了一个更广泛的代码示例来消除混淆。具体来说,样本现在是63行,比最初的30行有所增加。因此,变量名称已更改,因此引用Foo:p的注释应适用于Foo:link

一开始这是个坏主意,但作为空引用的解决方案,这是一个可怕的想法。

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


除此之外,指向自身的结构是一个多节的蠕虫罐头。考虑您的复印任务

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具有某种指针/引用语义,那么您的等式现在也是非平凡的

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

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

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

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

请考虑为该指针提供getter。

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

除非你分享更多关于Foo的内容,否则很难提供建议。

将类指针成员变量初始化为自身而不是NULLnullptr,这本身有什么不好的地方吗?

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

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

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

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

相关内容

  • 没有找到相关文章