引用成员变量作为类成员

时间:2012-09-12 11:34:08

标签: c++ reference

在我的工作地点,我看到这种风格被广泛使用: -

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};


int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

有没有名字来描述这个成语?我假设它是为了防止复制大型复杂对象的可能大量开销?

这通常是好的做法吗?这种方法有什么缺陷吗?

5 个答案:

答案 0 :(得分:74)

  

是否有名称来描述这个成语?

在UML中,它被称为聚合。它与组成的不同之处在于成员对象不是由引用类拥有的。在C ++中,您可以通过引用或指针以两种不同的方式实现聚合。

  

我假设是为了防止复制大型复杂对象的可能性很大?

不,这是使用它的一个非常糟糕的理由。聚合的主要原因是包含的对象不归包含对象所有,因此它们的生命周期不受约束。特别是,引用的对象生命周期必须比引用对象寿命长。它可能是早期创建的,并且可能存在于容器的生命周期结束之外。除此之外,引用对象的状态不受类控制,但可以在外部进行更改。如果引用不是const,那么该类可以更改位于其外部的对象的状态。

  

这通常是好的做法吗?这种方法有什么缺陷吗?

这是一个设计工具。在某些情况下,这将是一个好主意,在某些情况下它不会。最常见的缺陷是持有引用的对象的生命周期决不能超过引用对象的生命周期。如果封闭对象在之后使用引用,则引用的对象被销毁,您将具有未定义的行为。一般来说,最好选择合成到聚合,但如果你需要它,它就像其他任何工具一样好。

答案 1 :(得分:23)

它被称为dependency injection via constructor injection:class A将依赖项作为其构造函数的参数获取,并将对依赖类的引用保存为私有变量。

wikipedia上有一个有趣的介绍。

对于 const-correctness 我写道:

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

但是这个类的一个问题是它接受对临时对象的引用:

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

最好添加(至少需要C ++ 11):

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

<击> 无论如何,如果你改变了构造函数:

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

它几乎可以保证you won't have a pointer to a temporary

此外,由于构造函数使用指针,因此A的用户更清楚他们需要注意它们传递的对象的生命周期。


有些相关主题是:

答案 2 :(得分:17)

  

是否有名称来描述这个成语?

此用法没有名称,它简称为“引用为类成员”

  

我假设是为了防止复制大型复杂对象的可能性很大?

是以及您希望将一个对象的生命周期与另一个对象相关联的场景。

  

这通常是好习惯吗?这种方法有什么缺陷吗?

取决于您的使用情况。使用任何语言功能就像“为课程选择马匹”。值得注意的是,每个(几乎所有)语言功能都存在,因为它在某些情况下很有用。
使用引用作为类成员时,需要注意几点:

  • 您需要确保在您的类对象存在之前保证引用的对象存在。
  • 您需要在构造函数成员初始值设定项列表中初始化该成员。你不能拥有 延迟初始化 ,这在指针成员的情况下是可能的。
  • 编译器不会生成副本分配operator=(),您必须自己提供。确定您的=运营商在这种情况下应采取的行动非常麻烦。所以基本上你的班级变成 不可分配
  • 引用不能是NULL或引用任何其他对象。如果需要重新安装,则无法使用引用,如指针一样。

对于大多数实际用途(除非你真的担心由于成员大小而导致高内存使用)只是拥有一个成员实例,而不是指针或引用成员应该就足够了。这可以节省您对引用/指针成员带来的其他问题的担忧,但是会牺牲额外的内存使用量。

如果必须使用指针,请确保使用智能指针而不是原始指针。这会让你的生活更容易用指针。

答案 3 :(得分:1)

会员参考通常被视为不良。与成员指针相比,它们使生活变得艰难。但它不是特别不正常,也不是一些特别命名的习语或事物。这只是别名。

答案 4 :(得分:1)

C ++提供了一种很好的机制来通过类/结构构造来管理对象的生命周期。这是C ++相对于其他语言的最佳功能之一。

当您通过ref或指针暴露成员变量时,它原则上违反了封装。这个习惯用法使得该类的消费者能够在没有它的情况下改变A的对象的状态(A)具有对它的任何知识或控制。它还使消费者能够在A的对象的生命周期之外保持指向A的内部状态的ref /指针。这是糟糕的设计。相反,可以重构类来保存指向共享对象的引用/指针(不拥有它),这些可以使用构造函数设置(命令生命时间规则)。共享对象的类可以设计为支持多线程/并发,视情​​况而定。