148

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

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,并将对象作为引用传递。

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

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

4

10 回答 10

167

我自己的经验法则:

  • 当您希望对象的生命周期依赖于其他对象的生命周期时,请使用引用成员:这是一种明确的说法,即在没有另一个类的有效实例的情况下不允许对象处于活动状态 - 因为没有赋值和通过构造函数获取引用初始化的义务。这是一种设计类的好方法,无需假设它的实例是否属于另一个类。您只假设他们的生活与其他实例直接相关。它允许您稍后更改使用类实例的方式(使用新的、作为本地实例、作为类成员、由管理器中的内存池生成等)
  • 在其他情况下使用指针:当您希望稍后更改成员时,请使用指针或 const 指针以确保仅读取指向的实例。如果该类型应该是可复制的,那么无论如何您都不能使用引用。有时您还需要在特殊函数调用(例如 init() )之后初始化成员,然后您别无选择,只能使用指针。但是:在所有成员函数中使用断言来快速检测错误的指针状态!
  • 如果您希望对象生命周期依赖于外部对象的生命周期,并且您还需要该类型是可复制的,则在构造函数中使用指针成员但引用参数这样您在构造时表明该对象的生命周期取决于在参数的生命周期中,但实现使用指针仍然是可复制的。只要这些成员仅通过副本更改,并且您的类型没有默认构造函数,该类型就应该满足这两个目标。
于 2009-05-21T10:59:09.203 回答
74

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

示例问题:

  • 您被迫在每个构造函数的初始化列表中初始化引用:无法将此初始化分解到另一个函数中(直到 C++0x,无论如何 编辑: C++ 现在有委托构造函数
  • 引用不能被反弹或为空。这可能是一个优势,但如果代码需要更改以允许重新绑定或成员为空,则成员的所有用途都需要更改
  • 与指针成员不同,引用不能轻易地被智能指针或迭代器替换,因为重构可能需要
  • 每当使用引用时,它看起来像值类型(.运算符等),但表现得像一个指针(可以悬空) - 例如谷歌样式指南不鼓励它
于 2009-05-21T10:41:37.397 回答
38

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

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

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

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

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

于 2009-05-21T09:53:56.137 回答
12

尽可能使用引用,必要时使用指针。

于 2009-05-21T10:00:38.337 回答
7

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

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

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

于 2009-05-21T09:55:00.720 回答
5

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

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

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

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

于 2009-05-21T11:08:42.647 回答
4

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

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

  • 永远不要使用引用,因为它会阻止分配
  • 如果您的班级负责删除,请使用 boost 的 scoped_ptr(比 auto_ptr 更安全)
  • 否则,使用指针或常量指针
于 2009-05-21T09:47:24.153 回答
2

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

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

于 2009-05-21T10:19:07.583 回答
2

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

于 2015-02-05T16:12:32.493 回答
-1

如果我正确理解了这个问题...

作为函数参数而不是指针的引用: 正如您所指出的,指针并不清楚谁拥有指针的清理/初始化。当你想要一个指针时首选一个共享点,它是 C++ 11 的一部分,当在接受指向数据的类的生命周期内不能保证数据的有效性时,首选一个 weak_ptr。也是 C++ 11 的一部分。使用引用作为函数参数可以保证引用不为空。你必须颠覆语言特性来解决这个问题,我们不关心松散的大炮编码器。

引用 aa 成员变量: 如上关于数据有效性。这保证了指向的数据,然后引用,是有效的。

将变量有效性的责任转移到代码中的更早一点不仅可以清理后面的代码(您的示例中的 A 类),而且还可以让使用的人清楚。

在您的示例中,这有点令人困惑(我真的会尝试找到更清晰的实现),B 使用的 A 在 B 的生命周期内得到保证,因为 B 是 A 的成员,所以参考加强了这一点并且(也许)更清楚。

并且以防万一(这是一个很低的可能性,因为它在您的代码上下文中没有任何意义),另一种选择是使用非引用的非指针 A 参数,将复制 A,并使范式无用 - 我真的不认为你的意思是这个作为替代。

此外,这还保证您无法更改引用的数据,其中可以修改指针。只有当引用/指向数据的指针不可变时,const 指针才会起作用。

如果 B 的 A 参数不能保证设置或可以重新分配,则指针很有用。有时弱指针对于实现来说过于冗长,很多人要么不知道弱指针是什么,要么只是不喜欢它们。

请撕开这个答案:)

于 2018-12-16T18:35:26.267 回答