2012-09-12 138 views
45

在我工作的地方我看到这种风格广泛使用: -参考成员变量的类成员

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

是否有来形容这个成语的名称?我假设它是为了防止复制大型复杂对象的可能的大开销?

这是一个很好的做法吗?这种方法有没有陷阱?

+2

如果您在成员变量中引用的对象在别处销毁并尝试通过类访问它,则可能出现的一个错误 – mathematician1975

回答

60

有没有一个名称来形容这个成语?

在UML中称为聚合。它不同于成分,因为成员对象不是所属的。在C++中,可以通过引用或指针以两种不同的方式实现聚合。

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

不,这将是一个非常糟糕的理由来使用它。聚合的主要原因是包含的对象不属于包含对象,因此它们的生命周期不受限制。特别是引用的对象的生命周期必须超过引用的。它可能早已创建,可能会超出容器使用期限的寿命。除此之外,引用对象的状态不受类的控制,但可以在外部进行更改。如果引用不是const,那么该类可以更改处于其外部的对象的状态。

这是一般的良好做法吗?这种方法有没有陷阱?

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

+1

“不,这将是一个非常糟糕的理由使用这个”。您能否详细说明这一点?有什么可以用来实现呢? – coincoin

+0

@coincoin:为了达到目的? –

+1

'防止可能大的开销复制大复杂对象的?' – coincoin

1

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

+13

您能否提供对支持的引用*通常被认为是不好的*? –

1

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

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

+0

但OP中的代码不包含任何成员变量的引用。它有一个参考成员变量。所以,伟大的观点,但看似无关。 –

16

有没有一个名字来形容这个习语呢?

有这个用法没有名字,它被简称为“作为参考类成员”

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

是的,以及您想要将一个对象的生命周期与另一个对象关联的场景。

这是一般的良好做法吗?这种方法有没有陷阱?

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

  • 您需要确保引用的对象是保证生存,直到你的类对象存在。
  • 您需要在构造函数成员初始值设定项列表中初始化成员。你不能有一个延迟初始化,这可能在指针成员的情况下。
  • 编译器不会生成复制分配operator=(),您将不得不自己提供一个。确定运营商在这种情况下应该采取什么行动是很麻烦的。所以基本上你的班级变成不可转让
  • 参考文献不能是NULL或作为引用任何其他对象。如果您需要重新安装,那么在指针的情况下不可能使用引用。

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

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

+0

“**我假设它是为了防止复制大型复杂对象的可能的大开销**是的 - 请详细说明为什么您认为此模式与避免复制有任何关联,因为我看不到任何复制对象。如果这个非'idiom'以某种方式将任何人的副本保存为其真实目的的副作用,那么他们的原始设计就会有致命的缺陷,只是用这种模式替换原来的设计不太可能达到他们期望的效果。 –

19

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

wikipedia有一个有趣的介绍。

对于常量,正确性我会写:

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用户,他们需要注意他们传递对象的生命周期。


有点相关的主题有: