返回对象的常量引用而不是副本

在重构一些代码时,我遇到了一些返回 std: : string 的 getter 方法:

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

当然,返回一个 const std::string&的接收器会更好?当前方法返回的副本效率不高。返回一个常量引用会导致什么问题吗?

99804 次浏览

我将它改为返回 const std: : string & 。如果不更改所有调用代码,调用方可能会复制结果,但不会引入任何问题。

如果有多个调用 name ()的线程,则会出现一个潜在的问题。如果返回引用,但随后更改基础值,则调用方的值将更改。但是现有的代码看起来并不是线程安全的。

看看迪马对一个潜在但不太可能的相关问题的回答吧。

那要看你需要做什么了。也许您希望所有的调用者在不更改类的情况下更改返回值。如果返回不起作用的常量引用。

当然,下一个参数是调用者可以创建自己的副本。但是,如果您知道如何使用该函数,并且知道无论如何都会发生这种情况,那么这样做可能会为您节省稍后编写代码的步骤。

可以想象,如果打电话的人真的想要一份拷贝,你可能会弄坏一些东西,因为他们打算修改原件,想要保留一份拷贝。然而,实际上,它更有可能只是返回一个常量引用。

最简单的做法是尝试它,然后测试它,看看它是否仍然有效,前提是您可以运行某种类型的测试。如果没有,我会先专注于编写测试,然后再继续重构。

这可能导致问题的唯一方法是调用方存储引用,而不是复制字符串,并在对象被销毁后尝试使用它。像这样:

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

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

编辑: Modern C + + (即 C + + 11或更高版本)支持 返回值优化,因此按值返回事物不再受到排斥。仍然应该注意按值返回极大的对象,但在大多数情况下应该没问题。

如果您更改为常量引用,那么该函数的典型用法不会中断的可能性非常大。

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

我通常返回 const & 除非我不能。QBziZ 给出了这种情况的一个例子。当然,QBziZ 还声称 std: : string 具有即写即拷的语义,这种语义在今天很少成立,因为 COW 在多线程环境中涉及大量开销。通过返回 const & 你让调用者有责任用字符串做正确的事情。但是因为您正在处理已经在使用的代码,所以可能不应该更改它,除非分析显示复制此字符串会导致大量性能问题。然后,如果你决定改变它,你将需要彻底测试,以确保你没有打破任何东西。希望与你合作的其他开发人员不要像 Dima 的回答那样做粗略的工作。

这重要吗?一旦使用了现代编译器最佳化,按值返回的函数将不会涉及副本,除非它们在语义上被要求。

请看 C + + lite 常见问题解答

Const 引用返回的一个问题是,如果用户编写如下代码:

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

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

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

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

所以我倾向于使用 const 引用返回(因为,无论如何,我只是更愿意发送一个引用,而不是希望编译器会优化额外的临时值) ,只要遵守以下约定: “如果你想让它超出我的对象的存在,他们会在我的对象毁灭之前复制它。”

一些 std: : string 的实现使用复制到写的语义共享内存,所以值返回和引用返回几乎一样有效,你不必担心生命周期问题(运行时为你做到了这一点)。

如果您担心性能,那么 做个基准(再怎么强调也不为过) ! ! !尝试这两种方法并测量收益(或缺乏收益)。如果一个更好,你真的关心,然后使用它。如果没有,那么更喜欢它提供的保护,再次提到其他人的终身问题的价值。

你知道他们怎么说假设..。

返回拷贝和返回引用之间的 分歧是:

  • 性能 : 返回引用可能更快,也可能不更快; 这取决于编译器实现如何实现 std::string(正如其他人指出的)。但是,即使返回引用,函数调用之后的赋值通常也会涉及到一个副本,如 std::string name = obj.name();

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

如果你想它 又快又安全使用 : share _ ptr。对象可以在内部将字符串存储为 shared_ptr并返回 shared_ptr。这样,对象就不会被复制,而且它总是安全的(除非用户使用 get()提取原始指针,并在对象超出作用域后对其进行处理)。

实际上,通过引用返回字符串 没有的另一个问题是 std::string通过 C _ str ()方法提供对内部 const char*的指针访问。这让我调试头疼了好几个小时。例如,假设我想从 foo 获取名称,并将其传递给 JNI,以便用于构造一个 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.

如果您的调用者将要做这类事情,那么最好使用某种类型的智能指针或常量引用,或者至少在 foo.name ()方法上有一个讨厌的警告注释头。我之所以提到 JNI,是因为以前的 Java 编码人员可能特别容易受到这种方法链接的影响,而这种方法链接在其他方面似乎是无害的。

返回对成员的引用将公开该类的实现。 这样可以防止类的更改。如果需要优化,可能对私有或受保护的方法有用。 C + + getter 应该返回什么