简体   繁体   English

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

[英]Returning a const reference to an object instead of a copy

Whilst refactoring some code I came across some getter methods that returns a std::string.在重构一些代码时,我遇到了一些返回 std::string 的 getter 方法。 Something like this for example:例如这样的事情:

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

Surely the getter would be better returning a const std::string& ?当然,吸气剂会更好地返回一个const std::string& The current method is returning a copy which isn't as efficient.当前方法正在返回一个效率不高的副本。 Would returning a const reference instead cause any problems?返回 const 引用会导致任何问题吗?

The only way this can cause a problem is if the caller stores the reference, rather than copy the string, and tries to use it after the object is destroyed.这可能导致问题的唯一方法是调用者存储引用而不是复制字符串,并在对象销毁后尝试使用它。 Like this:像这样:

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

However, since your existing function returns a copy, then you would not break any of the existing code.但是,由于您现有的函数返回一个副本,因此您不会破坏任何现有代码。

Edit: Modern C++ (ie C++11 and up) supports Return Value Optimization , so returning things by value is no longer frowned upon.编辑:现代 C++(即 C++11 及更高版本)支持返回值优化,因此不再反对按值返回内容。 One should still be mindful of returning extremely large objects by value, but in most cases it should be ok.仍然应该注意按值返回非常大的对象,但在大多数情况下应该没问题。

Actually, another issue specifically with returning a string not by reference, is the fact that std::string provides access via pointer to an internal const char* via the c_str() method.实际上,另外一个问题特别是与通过引用返回一个字符串,是这样的事实std::string提供了通过指针访问内部const char*经由c_str()方法。 This has caused me many hours of debugging headache.这让我在调试过程中头疼了好几个小时。 For instance, let's say I want to get the name from foo, and pass it to JNI to be used to construct a jstring to pass into Java later on, and that name() is returning a copy and not a reference.例如,假设我想从 foo 获取名称,并将其传递给 JNI 以用于构造一个 jstring,以便稍后传递给 Java,并且name()返回的是副本而不是引用。 I might write something like this:我可能会这样写:

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.

If your caller is going to be doing this kind of thing, it might be better to use some type of smart pointer, or a const reference, or at the very least have a nasty warning comment header over your foo.name() method.如果您的调用者打算做这种事情,最好使用某种类型的智能指针或常量引用,或者至少在您的 foo.name() 方法上有一个令人讨厌的警告注释标题。 I mention JNI because former Java coders might be particularly vulnerable to this type of method chaining that may seem otherwise harmless.我提到 JNI 是因为以前的 Java 编码人员可能特别容易受到这种在其他方面看起来无害的方法链的影响。

One problem for the const reference return would be if the user coded something like: const 引用返回的一个问题是,如果用户编码如下:

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

With a std::string return, the temporary object would remain alive and attached to str until str goes out of scope.使用std::string返回,临时对象将保持活动状态并附加到 str 直到 str 超出范围。

But what happens with a const std::string & ?但是const std::string &会发生什么? My guess is that we would have a const reference to an object that could die when its parent object deallocates it:我的猜测是,当它的父对象释放它时,我们会有一个对可能死亡的对象的 const 引用:

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

So my preference goes to the const reference return (because, anyway, I'm just more confortable with sending a reference than hoping the compiler will optimize the extra temporary), as long as the following contract is respected: "if you want it beyond my object's existence, they copy it before my object's destruction"所以我更喜欢 const 引用返回(因为,无论如何,我只是更喜欢发送引用而不是希望编译器优化额外的临时文件),只要遵守以下约定:“如果你想要它超越我的对象的存在,他们在我的对象销毁之前复制它”

Some implementations of std::string share memory with copy-on-write semantics, so return-by-value can be almost as efficient as return-by-reference and you don't have to worry about the lifetime issues (the runtime does it for you). std::string 的一些实现使用 copy-on-write 语义共享内存,因此按值返回几乎与按引用返回一样有效您不必担心生命周期问题(运行时给你)。

If you're worried about performance, then benchmark it (<= can't stress that enough) !!!如果您担心性能,请对其进行基准测试(<= 强调不够)!!! Try both approaches and measure the gain (or lack thereof).尝试两种方法并测量增益(或缺乏)。 If one is better and you really care, then use it.如果一个更好,并且您真的很在意,那就使用它。 If not, then prefer by-value for the protection it offers agains lifetime issues mentioned by other people.如果没有,那么更喜欢按价值来保护它提供的其他人提到的终生问题。

You know what they say about making assumptions...你知道他们对假设的看法...

Okay, so the differences between returning a copy and returning the reference are:好的,所以返回副本和返回引用之间的区别是:

  • Performance : Returning the reference may or may not be faster;性能:返回引用可能会更快,也可能不会更快; it depends on how std::string is implemented by your compiler implementation (as others have pointed out).这取决于编译器实现如何实现std::string (正如其他人指出的那样)。 But even if you return the reference the assignment after the function call usually involves a copy, as in std::string name = obj.name();但即使您返回引用,函数调用后的赋值通常也涉及副本,如std::string name = obj.name();

  • Safety : Returning the reference may or may not cause problems (dangling reference).安全:返回引用可能会也可能不会导致问题(悬空引用)。 If the users of your function don't know what they are doing, storing the reference as reference and using it after the providing object goes out of scope then there's a problem.如果您的函数的用户不知道他们在做什么,将引用存储为引用并在提供对象超出范围后使用它,那么就会出现问题。

If you want it fast and safe use boost::shared_ptr .如果你想快速安全地使用boost::shared_ptr Your object can internally store the string as shared_ptr and return a shared_ptr .您的对象可以在内部将字符串存储为shared_ptr并返回shared_ptr That way, there will be no copying of the object going and and it's always safe (unless your users pull out the raw pointer with get() and do stuff with it after your object goes out of scope).这样,就不会复制对象,并且它始终是安全的(除非您的用户使用get()取出原始指针并在您的对象超出范围后对其进行处理)。

I'd change it to return const std::string&.我将其更改为返回 const std::string&。 The caller will probably make a copy of the result anyway if you don't change all the calling code, but it won't introduce any problems.如果您不更改所有调用代码,调用者可能无论如何都会复制结果,但这不会引入任何问题。

One potential wrinkle arises if you have multiple threads calling name().如果您有多个线程调用 name(),则会出现一种潜在问题。 If you return a reference, but then later change the underlying value, then the caller's value will change.如果您返回一个引用,但随后更改了基础值,则调用者的值将更改。 But the existing code doesn't look thread-safe anyway.但是现有的代码无论如何看起来都不是线程安全的。

Take a look at Dima's answer for a related potential-but-unlikely problem.看看 Dima 对相关的潜在但不太可能的问题的回答。

It is conceivable that you could break something if the caller really wanted a copy, because they were about to alter the original and wanted to preserve a copy of it.可以想象,如果调用者真的想要副本,您可以破坏某些内容,因为他们即将更改原件并想要保留它的副本。 However it is far more likely that it should, indeed, just be returning a const reference.然而,它更有可能实际上只是返回一个 const 引用。

The easiest thing to do is try it and then test it to see if it still works, provided that you have some sort of test you can run.最简单的方法是尝试一下,然后测试它是否仍然有效,前提是您有某种可以运行的测试。 If not, I'd focus on writing the test first, before continuing with refactoring.如果没有,我会先专注于编写测试,然后再继续重构。

Odds are pretty good that typical usage of that function won't break if you change to a const reference.如果您更改为 const 引用,那么该函数的典型用法不会中断,可能性非常大。

If all of the code calling that function is under your control, just make the change and see if the compiler complains.如果调用该函数的所有代码都在您的控制之下,只需进行更改并查看编译器是否会报错。

Does it matter?有关系吗? As soon as you use a modern optimizing compiler, functions that return by value will not involve a copy unless they are semantically required to.一旦您使用现代优化编译器,按值返回的函数将不会涉及副本,除非它们在语义上需要。

See the C++ lite FAQ on this.请参阅C++ lite FAQ

Depends what you need to do.取决于你需要做什么。 Maybe you want to all the caller to change the returned value without changing the class.也许您希望所有调用者在不更改类的情况下更改返回值。 If you return the const reference that won't fly.如果您返回不会飞行的常量引用。

Of course, the next argument is that the caller could then make their own copy.当然,下一个论点是调用者可以制作自己的副本。 But if you know how the function will be used and know that happens anyway, then maybe doing this saves you a step later in code.但是,如果您知道如何使用该函数并且知道无论如何都会发生这种情况,那么这样做可能会在代码中节省一步。

I normally return const& unless I can't.我通常返回 const& 除非我不能。 QBziZ gives an example of where this is the case. QBziZ 举例说明了这种情况。 Of course QBziZ also claims that std::string has copy-on-write semantics which is rarely true today since COW involves a lot of overhead in a multi-threaded environment.当然 QBziZ 还声称 std::string 具有写时复制语义,这在今天很少正确,因为 COW 在多线程环境中涉及大量开销。 By returning const & you put the onus on the caller to do the right thing with the string on their end.通过返回 const & 你把责任放在调用者身上,用他们的字符串做正确的事情。 But since you are dealing with code that is already in use you probably shouldn't change it unless profiling shows that the copying of this string is causing massive performance problems.但是,由于您正在处理已经在使用的代码,因此您可能不应该更改它,除非分析表明复制此字符串会导致大量性能问题。 Then if you decide to change it you will need to test thouroughly to make sure you didn't break anything.然后,如果您决定更改它,则需要彻底测试以确保没有损坏任何东西。 Hopefully the other developers you work with don't do sketchy stuff like in Dima's answer.希望与您合作的其他开发人员不要像 Dima 的回答那样做粗略的事情。

Returning a reference to a member exposes the implementation of the class.返回对成员的引用公开了类的实现。 That's could prevent to change the class.这可能会阻止更改类。 May be usefull for private or protected methods incase the optimization is needed.如果需要优化,可能对私有或受保护的方法有用。 What should a C++ getter return C++ getter 应该返回什么

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM