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

mar*_*h44 126 c++ reference class-members

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

class A {};

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

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};
Run Code Online (Sandbox Code Playgroud)

因此B负责更新C的一部分.我通过lint运行代码并且它围绕引用成员发出错误:lint#1725.这谈到了对默认副本和分配的关注,这是公平的,但默认的副本和赋值也很糟糕,所以那里没有什么优势.

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

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

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

Kla*_*aim 141

我自己的经验法则:

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

  • “**当您希望对象的生命周期依赖于其他对象的生命周期时使用引用成员**”是糟糕的建议。这不是编译时依赖项。如果要确保对象 A 的生命周期应该依赖于对象 B,请使用智能指针,因为这可以保证 A 不会在 B 之前被销毁。 (2认同)

Jam*_*kin 53

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

示例问题:

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

  • 你提到的所有事情都是要避免的好事,所以如果参考有助于此 - 它们是好的而不是坏的.初始化列表是初始化数据的最佳位置.通常,您必须隐藏赋值运算符,而不必使用引用."不能反弹" - 好,重用变量是个坏主意. (61认同)
  • @Mykola:我同意你的看法.我更喜欢在初始化列表中初始化成员,我避免使用空指针,并且变量改变其含义肯定不好.我们的观点不同之处在于我不需要或希望编译器为我强制执行此操作.它不会让类更容易编写,我没有遇到它会捕获的这个区域中的错误,偶尔我会欣赏我从代码中获得的灵活性,这些代码一直使用指针成员(适当的智能指针). (4认同)
  • 詹姆斯,并不是说它使代码更容易编写,它使代码更容易阅读.使用引用作为数据成员,您永远不必怀疑它在使用时是否为null.这意味着您可以查看需要较少上下文的代码. (4认同)
  • 它使得单元测试和模拟*更难*,几乎不可能取决于使用了多少,结合'影响图爆炸'是恕我直言,引人注目的理由永远不会使用它们. (4认同)

Myk*_*yev 33

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

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

并且可能将一对或零关系描述为指针.

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

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

  • +1:我经历过。只有某些通用数据存储类需要assignemtn和copy-c'tor。对于更高级的业务对象框架,还应该使用其他复制技术,例如“不要复制唯一字段”和“添加到另一个父对象”等。因此,您可以使用高级框架来复制业务对象并禁止进行低级分配。 (2认同)
  • +1:某些协议点:防止定义赋值运算符的引用成员不是一个重要的参数.正确地说明不同的方式,并非所有对象都具有值语义.我也同意,我们不希望"if(p)"分散在整个代码中,没有任何逻辑原因.但正确的方法是通过类不变量:一个明确定义的类将不会怀疑成员是否为空.如果代码中的任何指针都可以为null,那么我希望得到很好的评论. (2认同)

ebo*_*ebo 11

尽可能使用引用,并在必要时使用指针.

  • 我读过这个,但我认为它是在谈论传递参数,而不是成员数据."引用通常出现在对象的皮肤上,指针放在内部." (4认同)
  • 是的,我认为这在成员参考中不是一个好建议。 (2认同)

Kon*_*lph 6

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

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

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


小智 6

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

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

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

这些规则很简单,并且对我有利.我留下了使用智能指针(但请,而不是auto_ptr)作为其他人的类成员的规则.

  • 对第一个问题不太满意,但分歧并不是重视理由的问题.+1 (2认同)
  • 我之所以投票,是因为"永远不会使用引用作为集体成员",以下理由对我来说是不对的.正如我的回答(以及对最佳答案的评论)所解释的那样,根据我自己的经验,有些情况下它只是一件好事.我想像你一样,直到我被雇用在一家公司,他们以一种好的方式使用它,它让我清楚地知道有些情况下它会有所帮助.事实上,我认为真正的问题更多的是语法和隐藏的含义,使得引用作为成员使用需要程序员理解他在做什么. (2认同)
  • 通过解释引用成员语义令人困惑的问题,可以改进这个答案.这个基本原理很重要,因为从表面上看,指针在语义上看起来更加模糊(它们可能没有被初始化,它们也没有告诉你底层对象的所有权). (2认同)