ChatGPT解决这个技术问题 Extra ChatGPT

我应该更喜欢成员数据中的指针还是引用?

这是一个简化的例子来说明这个问题:

class A {};

class B
{
    B(A& a) : a(a) {}
    A& a;
};

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};

所以 B 负责更新 C 的一部分。我通过 lint 运行代码,它抱怨引用成员:lint#1725。这谈到了照顾默认副本和分配这很公平,但是默认副本和分配对于指针也很糟糕,所以那里几乎没有优势。

我总是尽可能地尝试使用引用,因为裸指针不确定地引入了谁负责删除该指针。我更喜欢按值嵌入对象,但如果需要指针,我会在拥有指针的类的成员数据中使用 auto_ptr,并将对象作为引用传递。

当指针可能为空或可能更改时,我通常只会在成员数据中使用指针。是否有任何其他理由更喜欢指针而不是数据成员的引用?

是否可以说包含引用的对象不应该是可分配的,因为引用一旦初始化就不应更改?

“但默认复制和赋值对指针也不好”:这不一样:如果指针不是 const,则可以随时更改指针。引用通常总是 const! (如果您尝试将您的成员更改为“A& const a;”,您将看到这一点。编译器(至少是 GCC)会警告您即使没有“const”关键字,该引用仍然是 const。
这样做的主要问题是,如果有人做了 B b(A()),那么你就完蛋了,因为你最终得到了一个悬空的引用。

K
Klaim

我自己的经验法则:

当您希望对象的生命周期依赖于其他对象的生命周期时,请使用引用成员:这是一种明确的方式,表示您不允许对象在没有另一个类的有效实例的情况下处于活动状态 - 因为没有赋值和通过构造函数获取引用初始化的义务。这是一种设计类的好方法,无需假设它的实例是否属于另一个类。您只假设他们的生活与其他实例直接相关。它允许您稍后更改使用类实例的方式(使用新的、作为本地实例、作为类成员、由管理器中的内存池生成等)

在其他情况下使用指针:当您希望稍后更改成员时,请使用指针或 const 指针以确保仅读取指向的实例。如果该类型应该是可复制的,那么无论如何您都不能使用引用。有时您还需要在特殊函数调用(例如 init() )之后初始化成员,然后您别无选择,只能使用指针。但是:在所有成员函数中使用断言来快速检测错误的指针状态!

如果您希望对象生命周期依赖于外部对象的生命周期,并且您还需要该类型是可复制的,则在构造函数中使用指针成员但引用参数这样您在构造时表明该对象的生命周期取决于在参数的生命周期中,但实现使用指针仍然是可复制的。只要这些成员仅通过副本更改,并且您的类型没有默认构造函数,该类型就应该满足这两个目标。


我认为你和 Mykola 的答案是正确的,并且促进了无指针(少读指针)编程。
备注:移动对象使用与非移动对象(同一类)相同的析构函数进行破坏。许多非默认析构函数的情况,也需要非默认移动构造函数,并且需要某种方式来区分移出和非移出的情况。为此,可以将指针成员变量重置为 nullptr,而引用成员变量将被保留。
“当你希望你的对象的生命依赖于其他对象的生命时,使用引用成员”是一个糟糕的建议。这不是编译时依赖。如果您想确保对象 A 的生命周期应该依赖于对象 B,请使用智能指针,因为这样可以保证 A 在 B 之前不会被销毁。
J
James Hopkin

避免引用成员,因为它们限制了类的实现可以做什么(包括,正如你提到的,阻止赋值运算符的实现)并且对类可以提供的功能没有任何好处。

示例问题:

您被迫在每个构造函数的初始化列表中初始化引用:无法将此初始化分解到另一个函数中(直到 C++0x,无论如何编辑:C++ 现在有委托构造函数)

引用不能被反弹或为空。这可能是一个优势,但如果代码需要更改以允许重新绑定或成员为空,则成员的所有用途都需要更改

与指针成员不同,引用不能轻易地被智能指针或迭代器替换,因为重构可能需要

每当使用引用时,它看起来像值类型(. 运算符等),但行为像指针(可以悬空) - 例如 Google 样式指南不鼓励它


您提到的所有事情都是要避免的好事情,因此,如果参考对此有所帮助-它们是好的而不是坏的。初始化列表是初始化数据的最佳位置。很多时候,您必须隐藏赋值运算符,而不必使用引用。 “不能反弹” - 好,重用变量是个坏主意。
@Mykola:我同意你的看法。我更喜欢在初始化器列表中初始化成员,我避免使用空指针,而且变量改变其含义当然不好。我们意见不同的地方是我不需要或不希望编译器为我强制执行此操作。它并没有使类更容易编写,我在这个领域没有遇到它会捕获的错误,而且偶尔我很欣赏我从始终使用指针成员(适当的智能指针)的代码中获得的灵活性。
我认为不必隐藏分配是一个虚假的论点,因为如果该类包含一个无论如何都不负责删除的指针,则您不必隐藏分配-默认值会这样做。我认为这是最好的答案,因为它给出了为什么指针应该优先于成员数据中的引用的充分理由。
詹姆斯,并不是说它使代码更容易编写,而是它使代码更容易阅读。使用引用作为数据成员,您永远不必怀疑它在使用时是否可以为空。这意味着您可以在需要较少上下文的情况下查看代码。
它使单元测试和模拟变得更加困难,几乎不可能取决于使用了多少,再加上“影响图爆炸”,恕我直言,有足够的理由从不使用它们。
M
Mykola Golubyev

对象很少应该允许分配和其他类似比较的东西。如果您考虑一些具有“部门”、“员工”、“主管”等对象的业务模型,则很难想象将一名员工分配给其他员工的情况。

因此,对于业务对象,将一对一和一对多关系描述为引用而不是指针是非常好的。

也许可以将一或零关系描述为指针。

所以没有'我们不能分配'然后因素。许多程序员只是习惯了指针,这就是为什么他们会找到任何参数来避免使用引用。

将指针作为成员将迫使您或您的团队成员在使用前一次又一次地检查指针,并带有“以防万一”的注释。如果指针可以为零,那么指针可能被用作一种标志,这很糟糕,因为每个对象都必须扮演自己的角色。


+1:我也经历过。只有一些通用数据存储类需要assignemtn 和copy-c'tor。对于更高级的业务对象框架,应该有其他复制技术也可以处理诸如“不复制唯一字段”和“添加到另一个父级”等内容。所以你使用高级框架来复制你的业务对象并禁止低级分配。
+1:一些协议点:防止赋值运算符被定义的引用成员不是一个重要的论点。正如您以不同的方式正确陈述的那样,并非所有对象都具有值语义。我也同意我们不希望“if (p)”无缘无故地散布在代码中。但正确的方法是通过类不变量:定义良好的类将不会怀疑成员是否可以为空。如果代码中的任何指针都可以为空,我希望得到很好的注释。
@JamesHopkin 我同意精心设计的类可以有效地使用指针。除了防止 NULL 外,引用还保证您的引用已初始化(不需要初始化指针,因此它可以指向其他任何内容)。引用还告诉您有关所有权的信息——即对象不拥有该成员。如果您始终对聚合成员使用引用,那么您将非常确信指针成员是复合对象(尽管复合成员由指针表示的情况并不多)。
e
ebo

Use references when you can, and pointers when you have to.


我读过它,但我认为它是在谈论传递参数,而不是成员数据。 “引用通常出现在对象的皮肤上,而指针则出现在内部。”
是的,我认为在成员引用的背景下这不是一个好的建议。
K
Konrad Rudolph

在一些重要的情况下,根本不需要可分配性。这些通常是轻量级算法包装器,可以在不超出范围的情况下方便计算。此类对象是引用成员的主要候选对象,因为您可以确定它们始终拥有有效的引用并且永远不需要复制。

在这种情况下,请确保使赋值运算符(通常还有复制构造函数)不可用(通过从 boost::noncopyable 继承或将它们声明为私有)。

然而,正如用户 pts 已经评论的那样,对于大多数其他对象来说,情况并非如此。在这里,使用引用成员可能是一个大问题,通常应该避免。


佚名

由于每个人似乎都在发布一般规则,我将提供两个:

永远不要使用使用引用作为类成员。我从来没有在我自己的代码中这样做过(除了向自己证明我在这条规则中是正确的)并且无法想象我会这样做的情况。语义太混乱了,这真的不是引用的设计目的。

在将参数传递给函数(基本类型除外)或算法需要副本时,始终使用引用。

这些规则很简单,对我很有帮助。我将使用智能指针(但请不要使用 auto_ptr)作为类成员的规则留给其他人。


对第一个不太同意,但分歧不是重视理由的问题。 +1
(不过,我们似乎在与 SO 的好选民的争论中输了)
我投了反对票,因为“永远不要将引用用作班级成员”,并且以下理由对我来说是不正确的。正如我在回答中所解释的(并对最佳答案发表评论)以及根据我自己的经验,在某些情况下这只是一件好事。我和你一样想,直到我被一家公司雇用,他们以一种很好的方式使用它,这让我清楚地知道在某些情况下它会有所帮助。事实上,我认为真正的问题更多地在于语法和隐藏的含义,这使得引用作为成员的使用要求程序员了解他在做什么。
尼尔> 我同意,但让我投反对票的不是“意见”,而是“从不”的断言。无论如何,在这里争论似乎没有帮助,我将取消我的反对票。
可以通过解释参考成员语义令人困惑的地方来改进这个答案。这个基本原理很重要,因为从表面上看,指针在语义上似乎更加模糊(它们可能没有被初始化,并且它们没有告诉你底层对象的所有权)。
p
pts

是的:是否说包含引用的对象不应该是可分配的,因为引用一旦初始化就不应更改?

我对数据成员的经验法则:

永远不要使用引用,因为它会阻止分配

如果您的班级负责删除,请使用 boost 的 scoped_ptr(比 auto_ptr 更安全)

否则,使用指针或常量指针


当我需要分配业务对象时,我什至无法说出一个案例。
+1:我只是要小心 auto_ptr 成员 - 任何拥有它们的类都必须明确定义分配和复制构造(生成的不太可能是所需的)
@Mykola:我还体验到 98% 的业务对象不需要分配或复制。我通过添加到这些类的标题中的一个小宏来防止这种情况,这使得复制 c'tors 和 operator=() 成为私有而没有实现。所以在 98% 的对象中,我也使用了引用,而且它是安全的。
作为成员的引用可用于明确声明类实例的生命周期直接依赖于其他类(或相同)实例的生命周期。 “永远不要使用引用,因为它会阻止分配”假设所有类都必须允许分配。这就像假设所有类都必须允许复制操作......
我和rstevens的情况一样,我每天操作的课程中有一半以上不是要分配的。
D
Dani van der Meer

当指针可能为空或可能更改时,我通常只会在成员数据中使用指针。是否有任何其他理由更喜欢指针而不是数据成员的引用?

是的。代码的可读性。指针使成员更明显是一个引用(讽刺的是:)),而不是一个包含的对象,因为当您使用它时,您必须取消引用它。我知道有些人认为这是过时的,但我仍然认为它只是防止混淆和错误。


Lakos 在“Large-Scale C++ Software Design”中也对此进行了论证。
我相信 Code Complete 也提倡这一点,我并不反对。虽然它并没有过于引人注目......
S
StephenD

我建议不要引用数据成员,因为您永远不知道谁将从您的类中派生以及他们可能想要做什么。他们可能不想使用被引用的对象,但是作为一个引用你已经强迫他们提供一个有效的对象。我已经对自己做了足够多的事情来停止使用引用数据成员。