73

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

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

当然,吸气剂会更好地返回 a const std::string&? 当前方法正在返回一个效率不高的副本。返回一个 const 引用会导致任何问题吗?

4

12 回答 12

60

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

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

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

编辑:现代 C++(即 C++11 及更高版本)支持Return Value Optimization,因此不再不赞成按值返回内容。仍然应该注意按值返回非常大的对象,但在大多数情况下应该没问题。

于 2008-09-25T17:41:03.863 回答
32

实际上,另一个通过引用返回字符串的问题是,通过c_str()方法通过指针提供对内部的访问。这让我头疼了好几个小时。例如,假设我想从 foo 获取名称,并将其传递给 JNI 以用于构造 jstring 以稍后传递给 Java,这将返回一个副本而不是引用。我可能会写这样的东西:std::stringconst char*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 编码人员可能特别容易受到这种看似无害的方法链接的攻击。

于 2011-02-23T18:19:54.780 回答
18

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

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

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

但是 a 会发生什么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 引用返回(因为无论如何,我只是更愿意发送引用而不是希望编译器优化额外的临时),只要遵守以下合同:“如果你想要它超越我的对象的存在,他们在我的对象被破坏之前复制它”

于 2008-09-25T20:58:25.090 回答
10

std::string 的一些实现与写时复制语义共享内存,因此按值返回几乎与按引用返回一样有效您不必担心生命周期问题(运行时确实给你)。

如果您担心性能,请对其进行基准测试(<= 强调不够)!!!尝试这两种方法并测量增益(或缺乏增益)。如果一个更好并且你真的很在乎,那就使用它。如果不是,那么更喜欢按价值提供保护,以防止其他人提到的终身问题。

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

于 2008-09-25T21:33:09.220 回答
8

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

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

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

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

于 2009-03-31T14:58:05.843 回答
4

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

如果您有多个线程调用 name(),则会出现一种潜在的问题。如果您返回一个引用,但后来更改了基础值,那么调用者的值将会更改。但是现有的代码无论如何看起来都不是线程安全的。

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

于 2008-09-25T17:34:47.623 回答
3

可以想象,如果调用者真的想要一个副本,您可能会破坏某些东西,因为他们即将更改原件并希望保留它的副本。然而,实际上它更有可能只返回一个 const 引用。

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

于 2008-09-25T17:37:05.277 回答
1

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

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

于 2008-09-25T17:43:13.343 回答
1

有关系吗?一旦您使用现代优化编译器,按值返回的函数将不会涉及副本,除非它们在语义上是必需的。

请参阅C++ lite 常见问题解答

于 2008-09-25T18:59:03.067 回答
0

取决于你需要做什么。也许您希望所有调用者在不更改类的情况下更改返回值。如果您返回不会飞的 const 引用。

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

于 2008-09-25T17:35:41.600 回答
0

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

于 2008-09-25T18:33:16.843 回答
-1

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

于 2017-03-20T07:44:17.053 回答