我应该更喜欢会员数据中的指针或引用吗?

时间:2009-05-21 09:42:01

标签: c++ reference class-members

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

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,并将该对象作为参考传递。

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

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

10 个答案:

答案 0 :(得分:127)

我自己的经验法则:

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

答案 1 :(得分:47)

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

示例问题:

  • 你被迫在每个构造函数的初始化列表中初始化引用:没有办法将这个初始化分解为另一个函数(直到C ++ 0x,无论如何 编辑: C ++现在有delegating constructors
  • 引用不能反弹或为空。这可能是一个优点,但如果代码需要更改以允许重新绑定或成员为null,则成员的所有使用都需要更改
  • 与指针成员不同,引用不能被智能指针或迭代器轻易替换,因为重构可能需要
  • 每当使用引用时,它看起来像值类型(.运算符等),但行为类似于指针(可以悬挂) - 例如, Google Style Guide劝阻

答案 2 :(得分:32)

对象很少应该允许分配和比较之类的其他东西。如果您考虑使用“部门”,“员工”,“董事”等对象的某种商业模式,很难想象将一名员工分配给其他员工的情况。

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

可能将一对或零关系描述为指针。

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

将指针作为成员会强制您或团队成员在使用前反复检查指针,并使用“以防万一”注释。如果指针可以为零,那么指针可能被用作一种标志,这是不好的,因为每个对象都必须扮演自己的角色。

答案 3 :(得分:11)

答案 4 :(得分:6)

在一些重要案例中,根本不需要可转让性。这些通常是轻量级算法包装器,便于计算而不离开范围。这些对象是参考成员的主要候选对象,因为您可以确保它们始终拥有有效引用,并且永远不需要复制。

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

但是,正如用户pts已经评论过的那样,大多数其他对象也是如此。在这里,使用参考成员可能是一个巨大的问题,通常应该避免。

答案 5 :(得分:6)

由于每个人似乎都在制定一般规则,我会提供两个:

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

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

这些规则很简单,让我处于有利地位。我留下了使用智能指针(但请,而不是auto_ptr)作为其他人的类成员的规则。

答案 6 :(得分:4)

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

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

  • 从不使用引用,因为它会阻止分配
  • 如果您的班级负责删除,请使用boost的scoped_ptr(比auto_ptr更安全)
  • 否则,使用指针或const指针

答案 7 :(得分:2)

  

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

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

答案 8 :(得分:2)

我建议反对参考数据成员,因为你永远不知道谁会从你的班级派生出来以及他们可能想做什么。他们可能不想使用引用的对象,但作为引用,您必须强制它们提供有效的对象。 我已经对自己这样做了足以停止使用参考数据成员。

答案 9 :(得分:0)

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

引用作为函数参数而不是指针: 如您所指出的,指针并不能明确指出谁拥有指针的清理/初始化。当需要一个指针时,最好使用一个共享点,它是C ++ 11的一部分,而在通过接受指向数据的类的生命周期中不能保证数据的有效性时,则首选一个weak_ptr。也是C ++ 11的一部分 将引用用作功能参数可确保引用不为空。您必须颠覆语言功能才能解决此问题,我们也不关心松散的加农炮编码器。

引用一个成员变量: 如上所述,关于数据有效性。这样可以保证所指向的数据然后被引用都是有效的。

将变量有效性的责任移到代码中的较早位置,不仅清除了较新的代码(示例中的类A),而且使使用该代码的人清楚了。

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

以防万一(这是一个不太可能的说法,因为在您的代码上下文中它没有任何意义),另一种选择是,使用未引用的,非指针A的参数,将复制A并建立范例无用的-我真的不认为您是要这样替代。

此外,这还保证了您无法更改引用的数据(在此数据中可以修改指针)。只有在对数据的引用/指针不可更改的情况下,const指针才有效。

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

请将此答案拆开:)