我知道标题听起来很熟悉,因为有很多类似的问题,但我问的是问题的不同方面(我知道将事物放在堆栈上和将它们放在堆上之间的区别)。
在 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;
使用第一种方法我不必手动释放内存,但对我来说它使代码难以阅读。第二种方法的问题是,我必须记住 delete thing;
,这看起来不太好。我不想返回复制的值,因为它效率低下(我认为),所以问题来了
是否有第三种解决方案(不需要复制值)?
如果我坚持第一个解决方案有什么问题吗?
何时以及为什么应该使用第二种解决方案?
我不想返回复制的值,因为它效率低下
证明给我看。
查找 RVO 和 NRVO,以及 C++0x 移动语义。在 C++03 中的大多数情况下,out 参数只是让代码变得丑陋的好方法,而在 C++0x 中,使用 out 参数实际上会伤害自己。
只需编写干净的代码,按值返回。如果性能是一个问题,请对其进行分析(停止猜测),然后找出可以解决的方法。它可能不会从函数返回东西。
就是说,如果你死心塌地那样写,你可能想要做 out 参数。它避免了动态内存分配,这更安全且通常更快。它确实需要您在调用函数之前有某种方法来构造对象,这并不总是对所有对象都有意义。
如果要使用动态分配,至少可以将其放入智能指针中。 (无论如何都应该这样做)然后你不用担心删除任何东西,事情是异常安全的,等等。唯一的问题是它可能比按值返回慢!
只需创建对象并返回它
Thing calculateThing() {
Thing thing;
// do calculations and modify thing
return thing;
}
我认为如果您忘记优化而只编写可读代码(稍后您需要运行分析器 - 但不要预先优化),您会帮自己一个忙。
只需返回一个像这样的对象:
Thing calculateThing()
{
Thing thing();
// do calculations and modify thing
return thing;
}
这将调用 Things 上的复制构造函数,因此您可能希望自己实现它。像这样:
Thing(const Thing& aThing) {}
这可能会执行得慢一些,但它可能根本不是问题。
更新
编译器可能会优化对复制构造函数的调用,因此不会有额外的开销。 (就像评论中指出的dreamlax)。
Thing thing();
声明了一个返回 Thing
的本地函数,此外,标准允许编译器在您提供的情况下省略复制构造函数;任何现代编译器都可能会这样做。
您是否尝试过使用智能指针(如果 Thing 真的很大很重的对象),例如 shared_ptr:
std::shared_ptr calculateThing()
{
std::shared_ptr<Thing> thing(new Thing);
// .. some calculations
return thing;
}
// ...
{
std::shared_ptr<Thing> thing = calculateThing();
// working with thing
// shared_ptr frees thing
}
auto_ptr
已弃用;请改用 shared_ptr
或 unique_ptr
。
确定是否正在调用复制构造函数的一种快速方法是将日志记录添加到类的复制构造函数中:
MyClass::MyClass(const MyClass &other)
{
std::cout << "Copy constructor was called" << std::endl;
}
MyClass someFunction()
{
MyClass dummy;
return dummy;
}
呼叫someFunction
;您将获得的“调用了复制构造函数”行的数量将在 0、1 和 2 之间变化。如果您没有得到,那么您的编译器已经优化了返回值(允许这样做)。如果你得到的结果不是 0,并且你的复制构造函数非常昂贵,然后搜索从你的函数返回实例的替代方法。
首先你的代码有错误,你的意思是有 Thing *thing(new Thing());
,只有 return thing;
。
使用 shared_ptr
第一个解决方案在幼稚库中很常见。它有一些性能和语法开销,尽可能避免
仅当您可以保证不会抛出异常,或者当性能绝对关键时(您将在这甚至变得相关之前与 C 或程序集进行交互)时才使用第二种解决方案。
我不想返回复制的值,因为它效率低下
这可能不是真的。编译器可以进行优化以防止这种复制。
例如,GCC 进行了这种优化。在下面的程序中,既没有调用移动构造函数也没有调用复制构造函数,因为没有进行复制或移动。另外,请注意 c
的地址。即使对象 c
在函数 f()
内实例化,c
仍驻留在 main()
的堆栈帧中。
class C {
public:
int c = 5;
C() {}
C(const C& c) {
cout << "Copy constructor " << endl;
}
C(const C&& c) noexcept {
cout << "Move Constructor" << endl;
}
};
C f() {
int beforeC;
C c;
int afterC;
cout << &beforeC << endl; //0x7ffee02f26ac
cout << &c << endl; //0x7ffee02f2710 (notice: even though c is instantiated inside f(), c resides in the stack frame of main()
cout << &afterC << endl; //0x7ffee02f26a8
return c;
}
C g() {
C c = f(); ///neither copy constructor nor move constructor of C are called, since none is done
cout << &c << endl; //0x7ffee02f2710
return c;
}
int main() {
int beforeC;
C c = g(); ///neither copy constructor nor move constructor of C are called, since none is done
int afterC;
cout << &beforeC << endl; //0x7ffee02f2718
cout << &c << endl; //0x7ffee02f2710 (notice:even though c is returned from f,it resides in the stack frame of main)
cout << &afterC << endl; //0x7ffee02f270c
return 0;
}
我相信 C++ 专家会给出更好的答案,但我个人喜欢第二种方法。使用智能指针有助于解决忘记 delete
的问题,正如您所说,它看起来比必须事先创建一个对象(如果您想在堆上分配它仍然必须删除它)更干净。
不定期副业成功案例分享