有时将 class 指针成员变量初始化为自身的好习惯?
Sometimes a good practice to initialize a class pointer member variable to itself?
对于严格内部的 class,即 而不是 旨在用作 API 的一部分提供给外部客户端,将 class 指针成员变量初始化为自身而不是 NULL
或 nullptr
有什么内在的邪恶吗?
示例请看下面的代码。
#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 以改进我在此处使用的任何术语。
编辑
- 这个问题是本着其他良好实践问题的精神提出的,例如这个关于 deleting references 的问题。
- 已提供更广泛的代码示例以消除混淆。具体来说,样本现在是 63 行,比最初的 30 行有所增加。因此,变量名称已更改,因此引用
Foo:p
的注释应适用于 Foo:link
.
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);
}
不要指着自己。只是不要。
对于严格内部的 class,即 而不是 旨在用作 API 的一部分提供给外部客户端,将 class 指针成员变量初始化为自身而不是 NULL
或 nullptr
有什么内在的邪恶吗?
示例请看下面的代码。
#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 以改进我在此处使用的任何术语。
编辑
- 这个问题是本着其他良好实践问题的精神提出的,例如这个关于 deleting references 的问题。
- 已提供更广泛的代码示例以消除混淆。具体来说,样本现在是 63 行,比最初的 30 行有所增加。因此,变量名称已更改,因此引用
Foo:p
的注释应适用于Foo:link
.
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
ornullptr
?
没有。但正如您所指出的,根据用例的不同,可能会有不同的考虑因素。
我不确定这在大多数情况下是否相关,但在某些情况下对象需要保存其自身类型的指针,因此它确实与这些情况相关。
例如,单向链表中的一个元素将有一个指向下一个元素的指针,因此列表中的最后一个元素通常会有一个 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);
}
不要指着自己。只是不要。