C++中的getter应该返回什么?

59

对于一个 C++ 的 getter 方法,它应该返回一个非平凡类型,但是成员变量却是类或结构体的情况下,最佳实践是什么?

  1. 按值返回,例如:MyType MyClass::getMyType() { return mMyType; }
  2. 通过 const 引用返回:const MyType& MyClass::getMyType() { return mMyType; }
  3. 通过地址返回:MyType* MyClass::getMyType() { return &mMyType; }

其中

class MyType { /* ... */ };

class MyClass
{
  private:
     MyType mMyType;
}

我特别担心这种方法的以下用法。您能否详细说明如何影响对象的复制,以及如果function()想要保存它以供进一步使用,悬挂引用和野指针的危险。

MyType* savedPointer;

SomeType function(MyType* pointer) { savedPointer = pointer; };

a. 适用于1和2。

{
  MyType t = myClass.getMyType();
  function(&t);
}

// is savedPointer still valid here?

b. 适用于1和2。

{
  const MyType& t = myClass.getMyType();
  function(&t);
}

// is savedPointer still valid here?

c. 适用于1和2。

{
  MyType& t = myClass.getMyType();
  function(&t);
}

// is savedPointer still valid here?

d. 有效期为3个单位。

{
  MyType* t = myClass.getMyType();
  function(t);
}

// is savedPointer still valid here?

其中 myClass 是一个类型为 MyClass 的对象。


这很大程度上取决于上下文。您想在类内部看到修改(例如修改向量元素)还是仅显式地拥有该值,但永远不会对原始值进行任何修改?在我看来,指针通常不应使用。容器类型应通过引用或const引用返回,其余类型应通过值返回(甚至根本不需要getter!)。类的重点是数据封装(以及其他功能)。始终通过非const引用返回将是愚蠢的。 - stefan
你也可以返回智能指针,这将为你节省很多烦恼... - jbat100
@jbat100 在这里你 不能 返回一个智能指针。没有动态分配的对象(也不应该有)。 - James Kanze
@jbat100 在非常非常少的情况下,智能指针成员是有道理的(也许除了 unique_ptr,但在这种情况下,你不想返回 unique_ptr 的“副本”)。 - James Kanze
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/41522/discussion-between-jbat100-and-james-kanze - jbat100
显示剩余4条评论
5个回答

31

您可以同时提供const和非const版本:

MyType       & MyClass::getMyType()       { return mMyType; }
MyType const & MyClass::getMyType() const { return mMyType; }

在这种情况下,我不会提供指针版本,因为那意味着返回值可能是空指针,而在此情况下它永远不可能是。

然而,真正的问题是,您基本上让调用者直接访问内部对象。如果这是您的意图,则可以将数据成员公开。如果不是,则需要更努力地隐藏对象。

一种选择是保留MyType const &访问器,但提供更间接的方式来修改内部对象(例如setMyType(…)或更适合包含类级别要表达语义的内容)。


3
@Simple:当然。我正要说到那个问题… - Marcelo Cantos
9
这里需要注意的是,封装性会丢失,因此有人可能会认为您不妨将成员设为公共的。当然,使用getter方法可以让您以后更改实现方式。 - juanchopanza
2
@Simple:不是不可能的 - 你可以潜在地开始返回一个指向MyClass数组中多个对象之一的引用,或者是一个动态分配的实例(可能会按需创建),或者是一个线程特定的实例等。 - Tony Delroy
1
@Johan:有点像。调用者可以在访问一次后缓存该值。 - Marcelo Cantos
1
@Johan:缓存是不好的编码风格吗? - Marcelo Cantos
显示剩余8条评论

25
一般来说,你应该优先选择按值返回,除非你明确希望保证引用将指代一个成员(这暴露了你的部分实现,但在像std::vector<>::operator[]这样的情况下是可取的)。返回引用会阻止类的后续更改,因为这意味着你无法返回计算出的值。(如果类被设计为基类,这一点尤为重要,因为返回引用会对所有派生类产生这种限制。)
唯一应该返回指针的时候是在涉及查找或其他可能导致返回空指针的情况下。
如果分析器指示此处存在性能问题,并且调用点也可以处理const引用(不修改返回值,不涉及对象的生命周期),那么返回const引用可能是一种有效的优化。当然,它必须与对实现的额外约束进行权衡,但在某些情况下是合理的。

3

我建议始终返回一个const引用,如果需要修改返回值,请使用setter函数。


4
返回 const 引用会对类的实现造成重要限制,并且至少部分地削弱了拥有 getter 的目的。 - James Kanze
5
拥有getter的目的是为了访问存储的信息,而不是修改它。通过使getter函数返回const引用,您可以确保对象的状态只能通过setter进行修改。这样可以实现一些非常好的编译时优化,并且更容易捕获一些错误。如果您认为需要获取一个全新的项,您总是可以显式使用复制构造函数(如果您返回一个对象而不是引用,复制构造函数将被调用),或者如果您需要修改对象的大量内部数据,则可以提供一个辅助函数。 - Javier Castellanos
1
使用函数访问属性的目的,而不仅仅是使数据成员可见,是为了不将代码锁定在特定的实现中。 - James Kanze

2

应避免按值返回,例如:MyType MyClass::getMyType() { return mMyType; },因为这将复制对象的内容。我看不到您可能获得的好处,但我看到了性能上的缺点。

通过const引用返回:const MyType& MyClass::getMyType() { return mMyType; }通常是以这种方式使用的:

const MyType& MyClass::getMyType() const { return mMyType; }
MyType& MyClass::getMyType() { return mMyType; }

始终提供const版本。非const版本是可选的,因为它意味着有人可以修改您的数据。我建议您使用该选项。
按地址返回:MyType* MyClass::getMyType() { return &mMyType; }通常用于数据是可选的情况下。在使用之前,通常必须对其进行检查。
现在,在您的用例中,我强烈建议不要保存指针超过作用域。这往往会导致所有权问题。如果必须这样做,请查看shared_ptr
对于您的示例,有两种情况:
a.关闭大括号后,savedPointer将不再有效。
b、c和d。关闭大括号后,savedPointer仍然有效,但请注意,它不应超过myClass的生命周期。

通过const引用返回对类的实现施加了重要的限制,应仅在分析器指示必要时使用;按值返回是默认设置。 - James Kanze
6
我不同意,哪些限制?按值返回将始终创建副本......这本身就是一个很大的限制。 - Johan

0

a) MyType t = 将会为 1 和 2 都创建对象副本。当 t 超出作用域后,已保存的指针将不再有效。

b) 对于返回引用的情况,保存的指针将是有效的,但对于返回对象的情况则是无效的。对于引用,指针的生命周期将与 myClass 相同。当然,在 const 引用中,&t 将是一个 const t* 而不是一个 t*,因此在调用 function(MyType*) 时会失败。

c) 与 b 相同,尽管对于 2 来说代码是无效的,因为你不能将一个 const MyType& 强制转换为一个 MyType&。通常这是不好的做法,使用 const 形式会更加可接受。

d) savedPointer 将具有与 myClass 相同的生命周期。

我通常倾向于返回引用或 const 引用,具体取决于您希望能够对返回值做什么。如果您返回一个引用(非 const),则可以执行像 myClass.getMyType() = ... 这样的操作,而如果您返回 const 引用,则该对象是只读的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接