【问题标题】:When is it preferable to store data members as references instead of pointers?什么时候最好将数据成员存储为引用而不是指针?
【发布时间】:2009-06-05 17:23:01
【问题描述】:

假设我有一个对象Employee_Storage,其中包含一个数据库连接数据成员。该数据成员应该存储为指针还是引用?

  • 如果我将其存储为参考,我 不必做任何NULL 检查。 (NULL 检查到底有多重要?)

  • 如果我将它存储为指针,它是 更容易设置Employee_Storage (或MockEmployee_Storage) 测试目的。

一般来说,我一直习惯于将我的数据成员存储为引用。然而,这使得我的模拟对象难以设置,因为我现在必须传入 true/mock 对象,而不是能够传入 NULLs(可能在默认构造函数中)。

是否有一个好的经验法则可以遵循,特别是关注可测试性?

【问题讨论】:

    标签: c++ tdd reference mocking pointers


    【解决方案1】:

    如果引用是在构造时分配的,最好将引用存储为数据成员,并且确实没有理由更改它们。由于无法重新分配引用,因此它们非常有限。

    一般来说,我通常存储为指针(或某种形式的模板化智能指针)。这更加灵活 - 既可以用于测试(如您所提到的),也可以用于正常使用。

    【讨论】:

    • 关于仅在没有理由更改引用时才使用引用的观点 - 我不能通过使用 const 指针来完成同样的事情吗? (Object * const m_ptr
    • @Runcible:是的,虽然引用的使用比指针成员的使用“更好”一点,所以如果你真的想要这种行为,引用通常更干净。不过,我很少使用成员的引用 - 在大多数情况下它太不灵活了。
    • 我明白了。那么 NULL 检查呢?鉴于您正在使用指针成员,您是否必须在所有地方进行 NULL 检查?有人告诉我,过多的 NULL 检查只会导致偏执的编程和额外的代码膨胀。我很好奇你的方法是什么。
    • 这取决于 - 当我使用异常时,我尝试包装我的赋值并将 NULL 检查放入赋值函数中,但不一定在使用中。这为您提供了两全其美 - 在分配时进行所有检查,但在您使用它时不需要它,因为它保证被分配(或者您之前抛出)。
    • 我还要在这里补充一点, const int* const 比 const int& 更丑陋/令人困惑,而且我用过很多次。
    【解决方案2】:

    将引用存储为数据成员几乎是不可取的,而且很多时候这是不可能的。如果对象必须是可分配的(因为它们必须存储在标准库容器中),则不能使用引用。另外,引用不能被重新定位,所以一旦一个引用被一个对象初始化,它就不能指向另一个对象。

    有关此问题的更详细讨论,请参阅此问题 Should I prefer pointers or references in member data?

    【讨论】:

      【解决方案3】:

      我试图自己解决这个问题,所以不妨发布它。我的结论是,使用引用数据成员似乎不是一个好主意,因为当你去初始化它时,你可能会无意中创建一个别名。

      #include <iostream>
      using namespace std;
      class stuff
      {
      public:
      explicit stuff(int &a):x(a) //you have to initialize it here
      {
      //body intialization won't work
      };
      int& x; //reference data member
      };
      
      int main()
      {
      int A=100; 
      stuff B(A);//intialize B.x
      cout<<B.x<<endl;//outputs 100
      A=50;//change A;
      cout<<B.x<<endl; //outputs 50, so B.x is an alias of A.
      system("pause");
      return 0;
      }
      

      【讨论】:

      • 如果你将它作为指向该局部变量 A 的指针传递它会是一样的。
      【解决方案4】:

      如果有选择,我喜欢尽可能使用最受限制的类型。 因此,如果我不需要支持 null 对象,我更愿意声明一个

      Foo& m_foo;
      

      成员而不是成员

      Foo*const m_foo;
      

      成员,因为前面的声明记录了m_foo 不能为空的事实。 短期来看,优势并不大。但从长远来看,当您回到旧代码时,立即保证您不必担心m_foo 为空的情况非常有价值。

      还有其他方法可以达到类似的效果。我从事的一个项目他们不理解引用会坚持任何潜在的空指针后缀为“00”,例如m_foo00。有趣的是,boost::optional 似乎是support references,虽然我没有尝试过。或者,您可以在代码中乱扔断言。

      【讨论】:

      • Foo& m_foo;意外重新分配后的愉快调试。顺便说一句,有一个引用并不能保证一个有效的对象。它只会给你虚假的信心。
      • @Sergei:但是您不能重新分配参考。参见例如stackoverflow.com/a/728272/24283。不确定您可以对 C++ 中的其他问题做些什么,但这也会影响指针。 (请记住,这个答案是在 2009 年编写的,当时智能指针还没有成为标准的一部分,并且原始指针的使用在大多数代码库中都很普遍)。当然,现在有比以往更多的机会使用 lambda 绑定来击中自己的脚。
      • m_foo = 某事;将覆盖原始对象。您真的不想存储非常量引用。我想这更像是一个错字而不是一个建议。
      • 一个 const Foo& m_foo;会禁止我调用 Foo 对象的非常量 Foo::ping() 方法,所以这不是一个通用的解决方案。也许使用指针确实确实使某些事情更加明确,但是也许您的代码最终会充满非空断言/检查,这可以通过使用引用类型在编译时强制执行来避免。 C++ 是一种不寻常的语言,因为它为您提供了选择(参见 C:仅指针,或仅 Java/Python 等引用)......但大部分选择是品味问题(或本地风格指南所说的任何内容)。
      【解决方案5】:

      添加到这个问题..

      具有引用数据成员的类:

      • 您必须在构造时将值传递给对象(并非意外)
      • 打破了封装规则,因为引用的变量可以从外部类中更改,而类对象没有对其进行任何控制。 (我想唯一的用例可能是这样的,但出于一些非常特殊的原因。)
      • 防止创建赋值运算符。你要复制什么?
      • 您需要确保引用的变量在对象处于活动状态时不会被破坏

      【讨论】:

        猜你喜欢
        • 2010-10-13
        • 2013-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-06
        • 2011-08-08
        • 1970-01-01
        相关资源
        最近更新 更多