引用成员变量作为类成员


81

在我的工作场所,我看到这种风格被广泛使用:

#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
一个可能的陷阱是,如果您在成员变量中引用的对象在其他地方被破坏,而您尝试通过您的类访问它
mathematician1975

Answers:


114

有没有描述这个习语的名字?

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

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

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

这是一般的好习惯吗?这种方法有什么陷阱吗?

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


7
“不,那将是使用它的一个非常不好的理由”。您能否详细说明这一点?有什么可以用来实现这一目标的呢?
硬币币

@coincoin:到底要实现什么?
大卫·罗德里格斯(DavidRodríguez)-德里贝斯

3
to prevent the possibly large overhead of copying a big complex object?
硬币币

3
@underscore_d感谢您的回答。然后,当您不能使用它们中的任何一个时会发生什么。假设我们要在不同的类中共享同一对象。如果按值传递此成员对象,则最终得到每个类的对象的副本?因此解决方案是使用智能指针或引用来避免复制。不是吗
coincoin

2
@ plats1就是我刚刚写的,我知道。我的观点是,您可以使用智能指针或引用。
coincoin

33

通过构造函数注入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他们需要注意传递的对象的生存期。


一些相关的主题是:


20

有没有描述这个习语的名字?

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

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

是的,还需要将一个对象的生存期与另一个对象相关联的方案。

这是一般的好习惯吗?这种方法有什么陷阱吗?

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

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

对于大多数实际目的(除非由于成员大小而真正引起高内存使用),仅具有成员实例而不是指针或引用成员就足够了。这使您不必担心引用/指针成员带来的其他问题,尽管这会浪费额外的内存。

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


我认为这是为了防止复制大型复杂对象的可能大笔开销?是的。”-请详细说明为什么您认为此模式与避免复制有任何关系,因为我看不到任何东西。如果这种非“惯用语”以某种方式将副本保存为任何人的真实目的,那么它们的原始设计在开始时就存在致命缺陷,仅以这种模式就地替换它不可能达到他们的期望。
underscore_d

@underscore_d假设一个类需要大量的const数据,并且同一时刻可以有很多此类的实例,对于每个实例来说,拥有自己的const数据副本可能是不可接受的浪费。因此,将const引用保存到可以共享的数据的外部位置可以节省大量复制。shared_ptr不一定是一种解决方案,因为不必动态分配数据句柄。
不重要的

@不重要我不确定我的反对意见是在2016年。我一直将引用用作类成员。也许我担心仅用引用替换按值所有权会导致终身问题,并不一定是万能药或总可以1:1进行的事情。我不知道。
underscore_d

1

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

当您通过ref或指针暴露成员变量时,它在原则上违反了封装。这种习语使类的使用者可以更改A对象的状态,而无需A(A)对它的任何了解或控制。它也使消费者能够在A对象的生存期之外保持对A内部状态的引用/指针。这是一个糟糕的设计。取而代之的是,该类可以重构为持有共享对象的引用/指针(而不是它的拥有者),并且可以使用构造函数进行设置(授权生命周期规则)。可以视情况将共享库的类设计为支持多线程/并发。


2
但是OP中的代码不包含对任何成员变量的引用。它具有一个作为引用的成员变量。因此,优点很多,但看似无关。
underscore_d

-2

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


23
您能否提供通常被认为是不好的支持参考?
DavidRodríguez-dribeas 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.