将一个const引用返回给一个对象,而不是一个副本

在重构一些代码的同时,我遇到了一些返回std :: string的getter方法。 像这样的东西,例如:

class foo { private: std::string name_; public: std::string name() { return name_; } }; 

当然,getter会更好的返回一个const std::string& ? 目前的方法是返回一个效率不高的副本。 会返回一个const引用,而不是导致任何问题?

这可能会导致问题的唯一方法是调用方存储引用,而不是复制string,并尝试在对象被销毁后使用它。 喜欢这个:

 foo *pFoo = new foo; const std::string &myName = pFoo->getName(); delete pFoo; cout << myName; // error! dangling reference 

但是,既然你现有的函数返回一个副本,那么你不会破坏任何现有的代码。

实际上,另外一个不是通过引用返回string的问题是, std::string通过c_str()方法提供了通过指向内部const char*指针的访问。 这已经导致了我很多小时的debugging头痛。 例如,假设我想从foo获取名称,并将其传递给JNI以用于构build一个jstring,稍后将其传递给Java,并且该name()将返回一个副本而不是引用。 我可能会写这样的东西:

 foo myFoo = getFoo(); // Get the foo from somewhere. const char* fooCName = foo.name().c_str(); // Woops! foo.name() creates a temporary that's destructed as soon as this line executes! jniEnv->NewStringUTF(fooCName); // No good, fooCName was released when the temporary was deleted. 

如果你的调用者要做这样的事情,最好使用某种types的智能指针或者一个const引用,或者至less在你的foo.name()方法上有一个令人讨厌的警告注释头。 我提到了JNI,因为以前的Java编程人员可能特别容易受到这种可能看起来无害的方法链接的影响。

对于const引用返回的一个问题是,如果用户编码如下:

 const std::string & str = myObject.getSomeString() ; 

使用std::string返回时,临时对象将保持活动状态并附加到str,直到str超出范围。

但是const std::string & ?会发生什么? 我的猜测是,我们将有一个对象的const引用,当它的父对象释放它时可能死亡:

 MyObject * myObject = new MyObject("My String") ; const std::string & str = myObject->getSomeString() ; delete myObject ; // Use str... which references a destroyed object. 

所以我喜欢去const引用返回(因为无论如何,我只是比发送一个引用更舒适,而不是希望编译器会优化额外的临时),只要以下合同得到尊重:“如果你想超越我的对象的存在,他们复制它在我的对象的破坏之前“

std :: string的一些实现与copy-on-write语义共享内存,所以按值返回可以与通过引用返回几乎一样高效, 而且不必担心生命周期问题(运行时会它为你)。

如果你担心performance,然后基准 (<=不能强调)! 尝试两种方法,并衡量收益(或缺乏)。 如果一个更好,你真的关心,然后使用它。 如果不是的话,那么更倾向于保护它所提供的保护。

你知道他们说什么做假设…

好的,所以返回拷贝和返回参考之间的区别是:

  • 性能 :返回参考可能会也可能不会更快; 这取决于你的编译器实现如何实现std::string (就像别人指出的那样)。 但是,即使你返回引用,函数调用之后的赋值通常也包含一个副本,如std::string name = obj.name();

  • 安全性 :返回参考可能会或可能不会导致问题(悬挂参考)。 如果你的函数的用户不知道他们在做什么,把引用存储为引用,并在提供对象超出范围之后使用它,那么就有问题了。

如果你想快速和安全地使用boost :: shared_ptr 。 您的对象可以在内部将string存储为shared_ptr并返回shared_ptr 。 这样,就不会复制对象,而且它总是安全的(除非你的用户用get()取出原始指针,并且在你的对象超出范围之后用它做东西)。

我会改变它返回const std :: string&。 如果你不改变所有的调用代码,调用者可能会复制结果的副本,但是不会引入任何问题。

如果您有多个线程调用name(),则会出现一个潜在的折痕。 如果你返回一个引用,但是后来改变底层的值,那么调用者的值就会改变。 但是现有的代码无论如何看起来都不是线程安全的。

看看迪玛的答案是否有相关的潜在问题。

可以想象,如果主叫方真的想要一个副本,你可能会破坏一些东西,因为他们正在改变原来的东西,并希望保留副本。 然而,它确实应该只是返回一个const引用。

最简单的方法就是尝试一下,然后testing一下,看看它是否仍然有效,只要你有一些可以运行的testing。 如果不是的话,我会先着重写testing,然后再继续进行重构。

有关系吗? 只要您使用现代优化编译器,通过值返回的函数不会涉及副本,除非它们在语义上需要。

请参阅C ++ lite FAQ 。

如果您更改为常量引用,则该函数的典型用法不会中断。

如果所有调用该函数的代码都在您的控制之下,只需进行更改并查看编译器是否抱怨。

取决于你需要做什么。 也许你想要所有的调用者改变返回值而不改变类。 如果你返回不会飞的const引用。

当然,接下来的说法是主叫方可以自己制作副本。 但是如果你知道这个函数是如何被使用的并且知道会发生这种情况的话,那么也许这样做可以在以后的代码中保存一步。

我通常返回const,除非我不能。 QBziZ举了一个这样的例子。 当然,QBziZ也声称std :: string具有写时复制的语义,因为COW在multithreading环境中涉及大量的开销,所以现在很less是真的。 通过返回const,你把主叫方的责任做在与他们结束的string正确的事情。 但是因为你正在处理已经在使用的代码,所以你可能不应该改变它,除非分析表明这个string的复制导致了巨大的性能问题。 那么如果你决定改变它,你将需要进行彻底的testing,以确保你没有破坏任何东西。 希望与你合作的其他开发人员不要像Dima的回答那样做粗略的事情。

返回对成员的引用会暴露该类的实现。 这可能会阻止改变class级。 可能是有用的私人或受保护的方法,因此需要进行优化。 C ++ getter应该返回什么