如何在C ++中“返回对象”?


167

我知道标题听起来很耳熟,因为有许多类似的问题,但是我要问的是问题的另一个方面(我知道将事物放在堆栈上并将它们放在堆上的区别)。

在Java中,我总是可以返回对“本地”对象的引用

public Thing calculateThing() {
    Thing thing = new Thing();
    // do calculations and modify thing
    return thing;
}

在C ++中,要做类似的事情,我有2个选择

(1)每当需要“返回”对象时,我都可以使用引用

void calculateThing(Thing& thing) {
    // do calculations and modify thing
}

然后像这样使用它

Thing thing;
calculateThing(thing);

(2)或者我可以返回指向动态分配对象的指针

Thing* calculateThing() {
    Thing* thing(new Thing());
    // do calculations and modify thing
    return thing;
}

然后像这样使用它

Thing* thing = calculateThing();
delete thing;

使用第一种方法,我不必手动释放内存,但是对我来说,这会使代码难以阅读。第二种方法的问题是,我必须记住to delete thing;,这看起来不太好。我不想返回复制的值,因为它效率低下(我认为),所以这里出现了问题

  • 有没有第三种解决方案(不需要复制值)?
  • 如果我坚持第一个解决方案,有什么问题吗?
  • 什么时候以及为什么要使用第二种解决方案?

32
+1很好地提出了问题。
康坎

1
太学究了,说“函数返回某些东西”有点不准确。更正确地,评估函数调用会产生一个值。该值始终是一个对象(除非它是一个void函数)。区别在于该值是glvalue还是prvalue,这取决于声明的返回类型是否是引用。
Kerrek SB 2014年

Answers:


107

我不想返回复制的值,因为它效率低下

证明给我看。

查找RVO和NRVO,并使用C ++ 0x移动语义。在大多数情况下,在C ++ 03中,out参数只是使代码丑陋的好方法,而在C ++ 0x中,使用out参数实际上会伤害自己。

只需编写干净的代码,按值返回即可。如果性能存在问题,请对其进行概要分析(停止猜测),然后找到可以解决的方法。它可能不会从函数返回任何东西。


就是说,如果您像这样死定了,那么您可能想要执行out参数。它避免了动态内存分配,该分配更安全且通常更快。确实需要您在调用函数之前有某种方法来构造对象,但这并不总是对所有对象都有意义。

如果要使用动态分配,则最少可以做的就是将其放在智能指针中。(无论如何都应该始终这样做)然后,您不必担心删除任何内容,事物是异常安全的等等。唯一的问题是,它无论如何都比按值返回要慢!


10
@phunehehe:毫无疑问,您应该对代码进行概要分析并找出答案。(提示:否。)编译器非常聪明,他们不必浪费时间来复制内容。即使复制会花费一些钱,您仍然应该努力争取好的代码,而不是快速的代码。当速度成为问题时,易于优化好的代码。为您不知道的事情而丑陋的代码是没有问题的;特别是如果您实际上放慢了速度或什么也没做。而且,如果您使用的是C ++ 0x,则移动语义会将其变为非问题。
GManNickG

1
@GMan,回复:RVO:实际上,只有当您的调用者和被调用者恰好在同一个编译单元中时,这才是正确的,在现实世界中,这并不是大多数时候。因此,如果您的代码不是全部模板化(在这种情况下,全部都放在一个编译单元中),或者您进行了一些链接时优化(GCC仅从4.5开始提供),您将感到失望。
Alex B

2
@Alex:编译器在跨翻译单元的优化方面越来越好。(VC现在已经针对多个发行版进行了此操作。)
2010年

9
@Alex B:这是完全垃圾。许多非常常见的调用约定使调用方负责为大返回值分配空间,而被调用方则负责其构造。即使没有链接时间优化,RVO也可以在整个编译单元中愉快地工作。
CB Bailey

6
@查尔斯,检查后似乎是正确的!我撤回了我明显错误的陈述。
Alex B

41

只需创建对象并返回它

Thing calculateThing() {
    Thing thing;
    // do calculations and modify thing
     return thing;
}

我认为,如果您忘记了优化而只是编写可读的代码,您将对自己有所帮助(您稍后需要运行探查器-但无需预先优化)。


2
Thing thing();声明局部函数并返回Thing
dreamlax

2
Thing Thing()声明一个返回Thing的函数。您的函数体中没有构造Thing对象。
CB Bailey

@dreamlax @Charles @GMan有点晚了,但已解决。
2011年

这在C ++ 98中如何工作?我在CINT解释器上遇到错误,想知道这是由于C ++ 98还是CINT本身引起的!
xcorat

16

只需返回一个这样的对象:

Thing calculateThing() 
{
   Thing thing();
   // do calculations and modify thing
   return thing;
}

这将在Things上调用副本构造函数,因此您可能需要自己执行该实现。像这样:

Thing(const Thing& aThing) {}

这可能会慢一些,但可能根本不是问题。

更新资料

编译器可能会优化对复制构造函数的调用,因此不会有额外的开销。(就像dreamlax在评论中指出的那样)。


9
Thing thing();声明一个局部函数,返回a Thing,并且,在您出现的情况下,该标准允许编译器省略复制构造函数;任何现代编译器都可能会这样做。
dreamlax

1
通过实现复制构造函数可以带来一个好处,尤其是在需要深层复制的情况下。
mbadawi23 2014年

+1用于明确说明复制构造函数,尽管@dreamlax表示,编译器很可能会“优化”函数的返回代码,从而避免了对复制构造函数的不必要调用。
jose.angel.jimenez 2014年

在2018年的VS 2017中,它正在尝试使用move构造函数。如果删除了移动构造函数而没有删除复制构造函数,则它将无法编译。
安德鲁

11

您是否尝试过使用智能指针(如果Thing确实是又大又重的对象),例如auto_ptr:


std::auto_ptr<Thing> calculateThing()
{
  std::auto_ptr<Thing> thing(new Thing);
  // .. some calculations
  return thing;
}


// ...
{
  std::auto_ptr<Thing> thing = calculateThing();
  // working with thing

  // auto_ptr frees thing 
}

4
auto_ptr不推荐使用;使用shared_ptrunique_ptr代替。
MBraedley'17年

只是要在这里添加它...我已经使用c ++多年了,尽管不是专业地使用c ++。各种各样的问题,也不能真正帮助加快代码的速度。我宁愿只是使用RAII复制数据并自己管理指针。因此,我建议,如果可以的话,请避免使用智能指针。
安德鲁

8

确定是否调用复制构造函数的一种快速方法是将日志添加到类的复制构造函数中:

MyClass::MyClass(const MyClass &other)
{
    std::cout << "Copy constructor was called" << std::endl;
}

MyClass someFunction()
{
    MyClass dummy;
    return dummy;
}

致电someFunction; 您将获得的“被调用复制构造函数”行的数量将在0、1和2之间变化。如果没有获得,则说明编译器已经优化了返回值(可以这样做)。如果你没有得到0,和你的拷贝构造函数是贵的离谱,然后寻找替代办法从你的函数返回的实例。


1

首先,您在代码中有一个错误,您的意思是have Thing *thing(new Thing());和only return thing;

  • 使用shared_ptr<Thing>。取消引用它作为指针。当最后一次引用Thing包含的内容超出范围时,它将为您删除。
  • 第一种解决方案在幼稚库中很常见。它具有一些性能和语法开销,请尽可能避免
  • 仅在可以保证不引发任何异常或性能绝对关键时才使用第二种解决方案(在此之前,您将与C或程序集接口)。

0

我确信C ++专家会提供更好的答案,但是我个人比较喜欢第二种方法。使用智能指针可以解决遗忘的问题,delete正如您所说,它看起来比必须事先创建对象(如果要在堆上分配对象,仍然必须删除它)看上去更干净。

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.