开发者

Is returning a temp-object by reference possible

开发者 https://www.devze.com 2022-12-11 16:42 出处:网络
is it possible to return a reference from a function like in this example code: string &erase_whitespace(string &text)

is it possible to return a reference from a function like in this example code:

string &erase_whitespace(string &text)
{
    text.erase(**etc.**);
    return text;
}

Call:

string tex开发者_如何转开发t = erase_whitespace(string("this is a test"));
cout << test;

Does this code work? On Visual C++ it does not crash but it looks wrong.

Thanks


From § 12.2.3 of the C++ 2003 standard (draft)

Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.

§ 12.2.4:

There are two contexts in which temporaries are destroyed at a different point than the end of the full- expression. ...

§ 12.2.5:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. ... A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.

§8.5.3.5 is what determines when the reference must be a const type. It is possible for a temporary to be bound to a non-const reference if the temporary is an instance of a class that has a conversion operator that returns an appropriate reference (that's a mouthful). An example might be easier to understand:

class Foo {
    ...
    operator Bar&() const;
...
void baz(Bar &b);
...
    baz(Foo()); // valid
    baz(Bar()); // not valid

The last line isn't valid because of § 12.3.2.1, which states "A conversion function is never used to convert [an ...] object to the [...] same object type (or a reference to it)". You might be able to make it work using casting via an ancestor of Bar and a virtual conversion function.

An assignment is an expression (§ 5.17), thus the full-expression (§ 1.9.12) in your code is the assignment. This gives the following sequence (forgetting for the moment that a temporary string probably can't be bound to a non-const reference):

  1. A temporary string is created
  2. The temporary is bound to the string& text argument of erase_whitespace
  3. erase_whitespace does its thang.
  4. erase_whitespace returns a reference to the temporary
  5. The temporary is copied to string text
  6. The temporary is destroyed.

So all is kosher in this case. The problem case, as Mike Seymour points out, would be assigning the result of erase_whitespace to a reference. Note that this likely wouldn't cause an immediate problem, as the area that stored the string probably contains the same data it did before the temporary was destroyed. The next time something is allocated on the stack or heap, however...


If you're using Visual C, then it would be better to do the following:

string erase_whitespace (const string &input)
{
  string output = input.erase (...);
  return output;
}

It may look worse but the compiler, when building an optimised version, can utilise Named Return Value Optimisations which eliminates the overhead of returning by value (i.e. eliminates the copy constructors involved in returning by value). So, not only does it look right, it is probably more efficent.


There are two questions here.

First, returning a reference is allowed of course, an example:

struct myclass
{
    myclass& foo()
    { cout << "myclass::foo()"; return *this }
    myclass& bar()
    { cout << "myclass::bar()"; return *this }
};
...
myclass obj;
obj.foo().bar();

Second, passing a temporary object to a non-const reference is not allowed in C++ (discussed a lot on SO, just search for it):

// passing string("this is a test") is wrong
string text = erase_whitespace(string("this is a test"));

Unfortunately, some compilers (e.g. VC) allow this behavior, although it is not standard. If you turn your compiler warning level, you should get a warning at least.


It looks wrong and it is wrong since non-const references are not allowed to bind to rvalues. In your case the compiler seems to accept it as an extension. You shouldn't rely on it.

As for possible chrashes: No this works fine with this compiler extension enabled. But this would not:

string const& dangling_reference =
    erase_whitespace(string("this is a test"));

because the function returns a reference to a temporary object that will be destroyed. If you return by value instead, this line will be safe due to a special C++ rule (the life of the temporary will be extended).

Another disadvantage is that the function is mutating its argument. This might be an unexpected behaviour for a function which takes a string and also returns a string.

In case you wrote the function this way to improve performance you could try this one and measure it:

string erase_whitespace(string text)
{
    text.erase(**etc.**);
    string ret; ret.swap(text);
    return ret;
}

If you have a good compiler that can elide unnecessary copies this should perform very well. To be specific, a compiler could elide the copy for accepting the parameter by value if the function is invoked with an rvalue. Your compiler might also be smart enough to apply NRVO (named return value optimization). If it is that smart, the following code

string foo = erase_whithespace("  blah  ");

will not call any copy ctor of std::string. Swapping is only done because no compiler is currently able to apply NRVO when a parameter is returned.


I'm not sure how representative your example is, but your function looks fine, as long as you document that it modifies the passed-in object. It's the calling that is bogus. I don't see why you can't just write:

std::string text("this is a test");
erase_whitespace(text);
cout << test;

Which does exactly the same thing as what you would hope happens in your original code, i.e. make a std::string, strip its whitespace, and output it, while keeping the std::string object itself on the stack instead of in the heap, and minimizing copies. Of course, the std::string's real storage is still on the heap, but you get the benefit of C++ disposing of the thing when the function or block is done.

I don't see why you need to save that line of code when it translates into no actual compiled code savings.

Now if you really needed this to, say, get stuffed into an equation, the usual thing to do is something like what Skizz wrote above (which works in regular C++, not just Visual C):

std::string erase_whitespace (const string &input)
{
    std::string output(input);
    output.erase (...);
    return output;
}

What's nice about this is that you can pass a const char* as the parameter, and C++ will automatically create a temporary std::string from it, since std::string has a constructor that takes a const char*. Also, this doesn't mess with the passed-in object. And as Skizz pointed out, optimizers like this kind of construct for return values.


Better would probably be to explicitly make it a heap allocation w/pointer:

string* erase_whitespace(string* text)
{
    text->erase(**etc.**);
    return text;
}

string* text = erase_whitespace(new string("this is a test"));
cout << *text;
delete text;


Your return parameter as well as the function parameter needs to const-reference. VC++ is non-compliant compiler in this regard and allows a temporary to be passed as a nonconst-reference.


I concur with Mike, but let me also mention that I don't think passing references to string around is a good idea in the first place. I'm pretty sure the string class is light-weight to pass by value since it stores the actual character array by reference internally.


Ya definately it should work ..

since string text = erase_whitespace(string("this is a test"))

is converted to following code by compiler :

string temp("this is a test");

string text = erase_whitespace(temp);


Well in the situation you give it DOES work. Your problem comes when you try and use the return as a reference. ie:

string& text = erase_whitespace(string("this is a test"));

This is perfectly valid VC++ code but you are now well into "undefined" territory. Your main problem comes from the fact that anyone else using this code will not know, without looking at the implementation, that they cannot do this.

All in its a very dangerous bit off code that will only work on VC++.

Skizz's response gives you code that will work perfectly as a drop-in replacement and fix ALL the problem's mentioned above.

0

精彩评论

暂无评论...
验证码 换一张
取 消