101

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

#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;
}

有什么名字可以形容这个成语吗?我假设这是为了防止复制大型复杂对象可能产生的巨大开销?

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

4

5 回答 5

136

有什么名字可以形容这个成语吗?

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

我假设这是为了防止复制大型复杂对象可能产生的巨大开销?

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

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

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

于 2012-09-12T12:30:13.363 回答
46

它被称为通过构造函数注入的依赖注入:类A将依赖项作为其构造函数的参数获取,并将对依赖类的引用保存为私有变量。

维基百科上有一个有趣的介绍。

对于const 正确性,我会写:

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;
};

几乎可以保证你不会有一个指向临时的指针

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


一些相关的主题是:

于 2014-09-15T10:23:16.450 回答
26

有什么名字可以形容这个成语吗?

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

我假设这是为了防止复制大型复杂对象可能产生的巨大开销?

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

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

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

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

对于大多数实际目的(除非您真的担心由于成员大小而导致的高内存使用),只需拥有一个成员实例,而不是指针或引用成员就足够了。这使您不必担心引用/指针成员带来的其他问题,尽管会以额外的内存使用为代价。

如果必须使用指针,请确保使用智能指针而不是原始指针。有了指针,您的生活就会变得更加轻松。

于 2012-09-12T11:55:02.620 回答
1

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

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

于 2012-09-12T11:54:54.167 回答
-3

成员引用通常被认为是不好的。与成员指针相比,它们使生活变得艰难。但这并不是特别不寻常,也不是什么特殊的成语或东西。这只是别名。

于 2012-09-12T11:38:21.440 回答