返回对对象的const引用,而不是副本

时间:2020-03-06 14:44:10  来源:igfitidea点击:

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

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

当然,getter会更好地返回const std :: string&?当前方法正在返回效率不高的副本。返回const引用会引起任何问题吗?

解决方案

我将其更改为返回const std :: string&。如果我们不更改所有调用代码,则调用者可能仍会复制结果,但不会带来任何问题。

如果我们有多个调用name()的线程,则可能会引起皱纹。如果返回引用,但随后又更改了基础值,则调用者的值将更改。但是现有的代码无论如何看起来都不是线程安全的。

看看迪玛(Dima)对于一个相关的潜在问题的答案,但这不太可能。

取决于我们需要做什么。也许我们希望所有调用者都更改返回的值而不更改类。如果返回的const引用将不起作用。

当然,下一个论点是调用者可以随后制作自己的副本。但是,如果我们知道该函数将如何使用并且无论如何都会发生,那么也许这样做可以为我们节省代码的后一步。

可以想象,如果呼叫者确实想要一份副本,我们可能会破坏某些内容,因为他们将要更改原始文件并想要保留其副本。但是,实际上,它更有可能应该只是返回一个const引用。

最简单的方法是尝试一下,然后对其进行测试,以查看它是否仍然有效,前提是我们可以进行某种测试。如果不是这样,那么我将专注于首先编写测试,然后再继续进行重构。

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

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

但是,由于现有函数返回一个副本,因此我们不会破坏任何现有代码。

很有可能,如果我们更改为const引用,则该函数的典型用法不会中断。

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

我通常会返回const&,除非我不能这样做。 QBziZ给出了这种情况的示例。当然,QBziZ还声称std :: string具有写时复制的语义,这在当今很少见,因为在多线程环境中COW涉及许多开销。通过返回const&,我们可以将调用者的责任放在调用者身上,以便在字符串的末尾进行正确的操作。但是,由于我们正在处理已在使用的代码,因此我们可能不应该更改它,除非分析表明复制此字符串会导致严重的性能问题。然后,如果我们决定更改它,则需要进行彻底测试以确保我们没有损坏任何东西。希望与我们一起工作的其他开发人员不要像Dima的回答那样做一些粗略的事情。

有关系吗?使用现代优化的编译器后,按值返回的函数将不包含副本,除非语义上需要它们。

有关此信息,请参见C ++ lite常见问题解答。

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的某些实现使用写时复制语义共享内存,因此按值返回几乎可以与按引用返回一样高效,并且我们不必担心生命周期问题(运行时确实给你)。

如果我们担心性能,请对其进行基准测试(<=不能足够强调)!!!尝试两种方法并测量增益(或者增益不足)。如果一个更好,并且我们真的在乎,请使用它。如果不是这样,则更喜欢按值来提供保护,它还会提供其他人提到的终生问题。

你知道他们说些什么...

好的,因此返回副本和返回引用之间的区别是:

  • 性能:返回参考可能更快,也可能不会更快;这取决于编译器实现如何实现" std :: string"(正如其他人指出的那样)。但是,即使我们返回引用,函数调用后的赋值通常也包含一个副本,如std :: string name = obj.name();
  • 安全:返回参考可能会或者可能不会导致问题(悬挂参考)。如果函数的用户不知道自己在做什么,将引用存储为引用,并在提供对象超出范围后使用它,则存在问题。

如果我们想快速安全地使用boost :: shared_ptr。对象可以在内部将字符串存储为shared_ptr并返回shared_ptr。这样,就不会复制任何对象,而且它始终是安全的(除非用户使用get()提取原始指针,然后在对象超出范围后对其进行处理)。