const references to temporary objects [duplicate] - c++

Does C++ provide a guarantee for the lifetime of a temporary variable that is created within a function call but not used as a parameter? Here's an example class:
class StringBuffer
{
public:
StringBuffer(std::string & str) : m_str(str)
{
m_buffer.push_back(0);
}
~StringBuffer()
{
m_str = &m_buffer[0];
}
char * Size(int maxlength)
{
m_buffer.resize(maxlength + 1, 0);
return &m_buffer[0];
}
private:
std::string & m_str;
std::vector<char> m_buffer;
};
And here's how you would use it:
// this is from a crusty old API that can't be changed
void GetString(char * str, int maxlength);
std::string mystring;
GetString(StringBuffer(mystring).Size(MAXLEN), MAXLEN);
When will the destructor for the temporary StringBuffer object get called? Is it:
Before the call to GetString?
After GetString returns?
Compiler dependent?
I know that C++ guarantees that a local temporary variable will be valid as long as there's a reference to it - does this apply to parent objects when there's a reference to a member variable?
Thanks.

The destructor for that sort of temporaries is called at the end of the full-expression. That's the most outer expression which is not part of any other expression. That is in your case after the function returns and the value is evaluated. So, it will work all nice.
It's in fact what makes expression templates work: They can keep hold references to that sort of temporaries in an expression like
e = a + b * c / d
Because every temporary will last until the expression
x = y
Is evaluated completely. It's quite concisely described in 12.2 Temporary objects in the Standard.

litb's answer is accurate. The lifetime of the temporary object (also known as an rvalue) is tied to the expression and the destructor for the temporary object is called at the end of the full expression and when the destructor on StringBuffer is called, the destructor on m_buffer will also be called, but not the destructor on m_str since it is a reference.
Note that C++0x changes things just a little bit because it adds rvalue references and move semantics. Essentially by using an rvalue reference parameter (notated with &&) I can 'move' the rvalue into the function (instead of copying it) and the lifetime of the rvalue can be bound to the object it moves into, not the expression. There is a really good blog post from the MSVC team on that walks through this in great detail and I encourage folks to read it.
The pedagogical example for moving rvalue's is temporary strings and I'll show assignment in a constructor. If I have a class MyType that contains a string member variable, it can be initialized with an rvalue in the constructor like so:
class MyType{
const std::string m_name;
public:
MyType(const std::string&& name):m_name(name){};
}
This is nice because when I declare an instance of this class with a temporary object:
void foo(){
MyType instance("hello");
}
what happens is that we avoid copying and destroying the temporary object and "hello" is placed directly inside the owning class instance's member variable. If the object is heavier weight than a 'string' then the extra copy and destructor call can be significant.

After the call to GetString returns.

I wrote almost exactly the same class:
template <class C>
class _StringBuffer
{
typename std::basic_string<C> &m_str;
typename std::vector<C> m_buffer;
public:
_StringBuffer(std::basic_string<C> &str, size_t nSize)
: m_str(str), m_buffer(nSize + 1) { get()[nSize] = (C)0; }
~_StringBuffer()
{ commit(); }
C *get()
{ return &(m_buffer[0]); }
operator C *()
{ return get(); }
void commit()
{
if (m_buffer.size() != 0)
{
size_t l = std::char_traits<C>::length(get());
m_str.assign(get(), l);
m_buffer.resize(0);
}
}
void abort()
{ m_buffer.resize(0); }
};
template <class C>
inline _StringBuffer<C> StringBuffer(typename std::basic_string<C> &str, size_t nSize)
{ return _StringBuffer<C>(str, nSize); }
Prior to the standard each compiler did it differently. I believe the old Annotated Reference Manual for C++ specified that temporaries should clean up at the end of the scope, so some compilers did that. As late as 2003, I found that behaviour still existed by default on Sun's Forte C++ compiler, so StringBuffer didn't work. But I'd be astonished if any current compiler was still that broken.

StringBuffer is in the scope of GetString. It should get destroyed at the end of GetString's scope (ie when it returns). Also, I don't believe that C++ will guarantees that a variable will exist as long as there is reference.
The following ought to compile:
Object* obj = new Object;
Object& ref = &(*obj);
delete obj;

From cppreference:
All temporary objects are destroyed as the last step in evaluating the
full-expression that (lexically) contains the point where they were
created, and if multiple temporary objects were created, they are
destroyed in the order opposite to the order of creation. This is true
even if that evaluation ends in throwing an exception.

Related

Extending the lifetime of a temporary object without copying it

Consider the following code:
#include <utility>
#include <iostream>
struct object {
object(const object&) = delete;
object(object&&) = delete;
object() {std::clog << "object::object()\n";}
~object() {std::clog << "object::~object()\n";}
void operator()() const {std::clog << "object::operator()()\n";}
};
struct wrapper {
const object& reference;
void operator()() const {reference();}
};
template <class Arg>
wrapper function(Arg&& arg) {
wrapper wrap{std::forward<Arg>(arg)};
return wrap;
}
int main(int argc, char* argv[]) {
wrapper wrap = function(object{}); // Let's call that temporary object x
wrap();
return 0;
}
I am really surprised that it prints:
object::object()
object::~object()
object::operator()()
Question 1: Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?
Question 2: Is there any way to implement the wrapper so that it would extend the lifetime of x past the function call?
Note: The copy and move constructors of the object have been explicitly deleted to make sure only one instance of it exists.
Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?
Technically, the lifetime of the object is extended past the function call. It is not however extended past the initialization of wrap. But that's a technicality.
Before we dive in, I'm going to impose a simplification: let's get rid of wrapper. Also, I'm removing the template part because it too is irrelevant:
const object &function(const object &arg)
{
return arg;
}
This changes precisely nothing about the validity of your code.
Given this statement:
const object &obj = function(object{}); // Let's call that temporary object x
What you want is for the compiler to recognize that "object x" and obj refer to the same object, and therefore the temporary's lifetime should be extended.
That's not possible. The compiler isn't guaranteed to have enough information to know that. Why? Because the compiler may only know this:
const object &function(const object &arg);
See, it's the definition of function that associates arg with the return value. If the compiler doesn't have the definition of function, then it cannot know that the object being passed in is the reference being returned. Without that knowledge, it cannot know to extend x's lifetime.
Now, you might say that if function's definition is provided, then the compiler can know. Well, there are complicated chains of logic that might prevent the compiler from knowing at compile time. You might do this:
const object *minimum(const object &lhs, const object &rhs)
{
return lhs < rhs ? lhs : rhs;
}
Well, that returns a reference to one of them, but which one will only be determined based on the runtime values of the object. Whose lifetime should be extended by the caller?
We also don't want the behavior of code to change based on whether the compiler only has a declaration or has a full definition. Either it's always OK to compile the code if it only has a declaration, or it's never OK to compile the code only with a declaration (as in the case of inline, constexpr, or template functions). A declaration may affect performance, but never behavior. And that's good.
Since the compiler may not have the information needed to recognize that a parameter const& lives beyond the lifetime of a function, and even if it has that information it may not be something that can be statically determined, the C++ standard does not permit an implementation to even try to solve the problem. Thus, every C++ user has to recognize that calling functions on temporaries if it returns a reference can cause problems. Even if the reference is hidden inside some other object.
What you want cannot be done. This is one of the reasons why you should not make an object non-moveable at all unless it is essential to its behavior or performance.
As far as I know, the only case the lifetime if extended if for the return value of a function,
struct A { int a; };
A f() { A a { 42 }; return a`}
{
const A &r = f(); // take a reference to a object returned by value
...
// life or r extended to the end of this scope
}
In your code, you pass the reference to the "constructor" of A class. Thus it is your responsability to ensure that the passed object live longer. Thus, your code above contains undefined behavior.
And what you see would probably be the most probable behavior in a class that do not make reference to any member. If you would access object member (including v-table), you would most likely observe a violation access instead.
Having said that, the correct code in your case would be:
int main(int argc, char* argv[])
{
object obj {};
wrapper wrap = function(obj);
wrap();
return 0;
}
Maybe what you want is to move the temporary object into the wrapper:
struct wrapper {
wrapper(object &&o) : obj(std::move(o)) {}
object obj;
void operator()() const {obj();}
};
In any case, the original code does not make much sense because it is build around false assumption and contains undefined behavior.
The life of a temporary object is essentially the end of the expression in which it was created. That is, when processing wrap = function(object{}) is completed.
So in resume:
Answer 1 Because you try to apply lifetime extension to a context other that the one specified in the standard.
Answer 2 As simple as moving the temporary object into a permanent one.

Is this safe? Rvalue and reference return values

#include <iostream>
#include <string>
using namespace std;
class Wrapper
{
public:
std::string&& get() &&
{
std::cout << "rvalue" << std::endl;
return std::move(str);
}
const std::string& get() const &
{
std::cout << "lvalue" << std::endl;
return str;
}
private:
std::string str;
};
std::string foo()
{
Wrapper wrap;
return wrap.get();
}
std::string bar()
{
Wrapper wrap;
return std::move(wrap.get());
}
std::string fooRvalue()
{
return Wrapper().get();
}
std::string barRvalue()
{
return std::move(Wrapper().get());
}
int main() {
while(1)
{
std::string s = foo();
std::string s2 = bar();
std::string s3 = fooRvalue();
std::string s4 = barRvalue();
}
return 0;
}
Is the return value of const std::string&, and std::string&& safe in this use case (pretend std::string is some other type that may be not copyable). I thought it would not work because the refernce should point to some local variable which would go out of scope, but it appears to work just fine? Also i've seen the && syntax used before, did i use it correctly (i restricted it to when Wrapper is itself an rvalue).
Im just a little generally confused when returning a reference is safe, i thought the rule was that the object had to outlive the value being returned but i've seen things like the standard library and boost returing reference before. If i store the reference they give me in a variable (that is not a reference) it all seems to work just fine if i then return that stored variable. Can someone fix me up, and give me some good rules to follow.
Here's the ideone i was playing around with, it all seems to work: http://ideone.com/GyWQF6
Yes, this is a correct and safe way of implementing a member getter. But you could make one improvement, which I'll get to. The key is that the lifetime of the std::string member is (almost) the same as the lifetime of the Wrapper object which contains it. And a temporary is only destroyed at the end of the full-expression where it appears, not immediately after it's "used". So in return std::move(Wrapper().get());, the Wrapper is created, get() is called, std::move is called, the return value is constructed, and only then the Wrapper and its string are destroyed.
The only danger is if somebody binds another reference to your member. But that would be their mistake, and ordinary const-reference getters have the same danger. This is the equivalent of the incorrect const char* ptr = "oops"s.c_str();.
std::string foo()
{
Wrapper wrap;
return wrap.get();
}
foo copy constructs the return value from the member. Hopefully that's not a surprise.
std::string bar()
{
Wrapper wrap;
return std::move(wrap.get());
}
bar also copy constructs the return value from the member. What's going on here is that since wrap is an lvalue, the lvalue get() is called, which returns a const lvalue. (Informally, "const std::string&".) Then move converts that to an xvalue, but it's still const. (Informally, "const std::string&&".) The move constructor string(string&&) cannot match here, since the argument is const. But the copy constructor string(const string&) can match, and is called.
If you expected bar to move construct the return value, you may want to add a third overload for non-const lvalues:
std::string& get() & { return str; }
(If you didn't want to allow modifying the member, note that you already sort of did. For example, somebody could do std::move(wrap).get() = "something";.)
Also, if you had return std::move(wrap).get(); instead, that would have move constructed the return value even without the third overload.
std::string fooRvalue()
{
return Wrapper().get();
}
fooRvalue move constructs the return value, since the rvalue get() is used. As already mentioned, the Wrapper lives long enough for this construction to be safe.
std::string barRvalue()
{
return std::move(Wrapper().get());
}
In barRvalue, the move call is absolutely useless. The expression Wrapper().get() is already an xvalue, so move just converts an xvalue of type std::string to ... an xvalue of type std::string. Still just as safe, though.
The return value object of a function will be initialized before the destruction of local variables and temporaries created in the return expression. As such, it is perfectly legitimate to move from locals/temporaries into function return values.
Those four global functions work because they return objects, not references to objects. And that object gets constructed before any of the locals that their constructor parameter reference are destroyed.
barRvalue is rather redundant (since the result of Wrapper().get() is already an rvalue reference), but functional.
Im just a little generally confused when returning a reference is safe
A function returning a reference to an object that has been destroyed is what is unsafe. Returning a reference to a local object, or part of a local object, is unsafe.
Returning a reference to *this or some part of *this is safe, because the this object will outlive the function that returns the reference. It's no different from returning a reference to a parameter that you take by pointer/reference. The object will outlive the function call that returns the reference.

std::move changes address of variables?

I found unexpected (at least for me) behavior.
class A
{
char _text[100];
char* _beg;
char* _end;
public:
explicit A(char* text, size_t tsize) : _beg(&text[0]), _end(&text[std::min(tsize, 99)])
{
memcpy(_text, text, std::min(tsize, 99));
*_end = '\0';
}
inline std::string get_text()
{
return std::move(std::string(_beg, _end));
}
};
After that somewhere in code I do that:
A* add_A(A&& a)
{
list_a.push_back(std::move(a));
return &(list_a.back());
}
std::list<A> list_a;
{
add_A(A("my_text", 7));
list_a.back().get_text(); //returns "my_text"
}
list_a.back().get_text(); //returns trash
As only I move this class (using std::move), and call get_text() of object that was moved, I get trash, as if after movement address of variable _text changed, and so _beg and _end points to nowhere.
Does address of variables really can be changes after std::move (I thought move don't really move object, it was invented for that)?
If it can be changed, what is usual pattern to handle it (to change pointers accordingly)?
If it can't be change, may that behavior happens because I try to move such object to std::list (and so there somehow happens copying, it changes address of variables and makes pointers point to wrong positions)?
Moving in C++ is just a specialized form of copy, where you modify the data in the object being moved from. That's how unique_ptr works; you copy the pointer from one unique_ptr object to the other, then set the original value to NULL.
When you move an object, you are creating a new object, one who gets its data from another object. The address of members don't "change"; it's simply not the same object.
Because you didn't write a copy/move constructor, that means the compiler will write one for you. And all they do is copy each element. So the newly moved-to object will have pointers that point to the old object.
An object that is about to be destroyed.
It's like moving into a house that happens to look identical to your old one. No matter how much it looks like your old house, it isn't. You still have to change your address, since it's a new house. So too must the addresses of _beg and _end be updated.
Now, you could create a move constructor/assignment operator (along with a copy constructor/assignment operator) to update your pointers. But quite frankly, that's just wallpapering over bad design. It's not a good idea to have pointers to subobjects within the same object if you can help it. Instead of begin/end pointers, just have an actual size:
class A
{
char _text[100];
size_t _size;
public:
explicit A(char* text, size_t tsize) : _size(tsize)
{
strncpy(_text, text, 100);
}
inline std::string get_text()
{
return std::string(_text, _size); //Explicit `move` call is unnecessary
}
};
This way, there is no need to store begin/end pointers. Those can be synthesized as needed.
std::move has no moving parts, it simply promotes the input parameter to an rvalue reference -- remember that inside the body of foo(T&& t) { ... } the use of t by name evaluates as an lvalue (reference to rvalue).
inline std::string get_text()
{
return std::move(std::string(_beg, _end));
}
Breaking this down:
std::string(_beg, _end);
creates an anonymous, temporary std::string object constructed from _beg to _end. This is an rvalue.
std::move(...);
forcibly promotes this to an rvalue reference and prevents the compiler from performing return-value optimization. What you want is
return std::string(_beg, _end);
See assembly code comparison
You probably also want to use
list_a.emplace_back(std::move(a));
Unfortunately, there are two flaws in this approach.
The simpler is that the term moving can be a bit misleading, it sounds very one way. But in practice it is often a two way swap: the two objects exchange properties so that when the temporary object goes out of scope it performs cleanup of whatever the other object previously owned:
struct S {
char* s_;
S(const char* s) : s_(strdup(s)) {}
~S() { release(); }
void release() { if (s_) free(s_); }
S(const S& s) : s_(strdup(s.s_)) {}
S(S&& s) : s_(s.s_) { s.s_ = nullptr; }
S& operator=(const S& s) { release(); s_ = strdup(s); return *this; }
S& operator=(S&& s) { std::swap(s_, s.s_); return *this; }
};
Note this line:
S& operator=(S&& s) { std::swap(s_, s.s_); return *this; }
When we write:
S s1("hello");
s1 = S("world");
the second line invokes the move-assignment operator. The pointer for the copy of hello is moved into the temporary, the temporary goes out of scope and is destroyed, the copy of "hello" is freed.
Doing this swap with your array of characters is significantly less efficient than a one-way copy would be:
struct S {
char s_[100];
S(const S& s) {
std::copy(std::begin(s.s_), std::end(s.s_), std::begin(s_));
}
S(S&& s) {
char t_[100];
std::copy(std::begin(s.s_), std::end(s.s_), std::begin(t_));
std::copy(std::begin(s_), std::end(s_), std::begin(s.s_));
std::copy(std::begin(t_), std::end(t_), std::end(s_));
}
};
You don't have to do this, the rvalue parameter only needs to be in a safe to destroy state, but the above is what the default move operators are going to do.
The disasterous part of your code is that the default move operator is naive.
struct S {
char text_[100];
char *beg_, *end_;
S() : beg_(text_), end_(text_ + 100) {}
};
Consider the following copy-construction:
S s(S());
What does s.beg_ point to?
Answer: it points to S().text_, not s.text_. You would need to write a copy constructor that copied the contents of text_ and then pointed its own beg_ and end_ to its own text_ rather than copying the source values.
The same problem occurs with the move operator: it will move the contents of text_ but it will also move the pointers, and have no clue that they are relative.
You'll either need to write copy/move constructors and assignment operators, or you could consider replacing beg_ and end_ with a single size_t size value.
But in either case, move is not your friend here: you're not transferring ownership or performing a shallow copy, all of your data is inside your object.

is twice calls to copy constructor happen for this c++ code sample?

for method:
Object test(){
Object str("123");
return str;
}
then, I had two methods to call it:
code 1:
const Object &object=test();
code 2:
Object object=test();
which one is better? is twice calls to copy constructor happen in code 2 if without optimize?
other what's the difference?
for code2 I suppose:
Object tmp=test();
Object object=tmp;
for code1 I suppose:
Object tmp=test();
Object &object=tmp;
but the tmp will be deconstructor after the method.so it must add const?
is code 1 right without any issues?
Let's analyse your function:
Object test()
{
Object temp("123");
return temp;
}
Here you're constructing a local variable named temp and returning it from the function. The return type of test() is Object meaning you're returning by value. Returning local variables by value is a good thing because it allows a special optimization technique called Return Value Optimization (RVO) to take place. What happens is that instead of invoking a call to the copy or move constructor, the compiler will elide that call and directly construct the initializer into the address of the caller. In this case, because temp has a name (is an lvalue), we call it N(amed)RVO.
Assuming optimizations take place, no copy or move has been performed yet. This is how you would call the function from main:
int main()
{
Object obj = test();
}
That first line in main seems to be of particular concern to you because you believe that the temporary will be destroyed by the end of the full expression. I'm assuming it is a cause for concern because you believe obj will not be assigned to a valid object and that initializing it with a reference to const is a way to keep it alive.
You are right about two things:
The temporary will be destroyed at the end of the full expression
Initializing it with a reference to const will extend its life time
But the fact that the temporary will be destroyed is not a cause for concern. Because the initializer is an rvalue, its contents can be moved from.
Object obj = test(); // move is allowed here
Factoring in copy-elision, the compiler will elide the call to the copy or move constructor. Therefore, obj will be initialized "as if" the copy or move constructor was called. So because of these compiler optimizations, we have very little reason to fear multiple copies.
But what if we entertain your other examples? What if instead we had qualified obj as:
Object const& obj = test();
test() returns a prvalue of type Object. This prvalue would normally be destructed at the end of the full expression in which it is contained, but because it is being initialized to a reference to const, its lifetime is extended to that of the reference.
What are the differences between this example and the previous one?:
You cannot modify the state of obj
It inhibits move semantics
The first bullet point is obvious but not the second if you are unfamiliar with move semantics. Because obj is a reference to const, it cannot be moved from and the compiler cannot take advantage of useful optimizations. Assigning reference to const to an rvalue is only helpful in a narrow set of circumstances (as DaBrain has pointed out). It is instead preferable that you exercise value-semantics and create value-typed objects when it makes sense.
Moreover, you don't even need the function test(), you can simply create the object:
Object obj("123");
but if you do need test(), you can take advantage of type deduction and use auto:
auto obj = test();
Your last example deals with an lvalue-reference:
[..] but the tmp will be destructed after the method. So must we add const?
Object &object = tmp;
The destructor of tmp is not called after the method. Taking in to account what I said above, the temporary to which tmp is being initialized will be moved into tmp (or it will be elided). tmp itself doesn't destruct until it goes out of scope. So no, there is no need to use const.
But a reference is good if you want to refer to tmp through some other variable. Otherwise, if you know you will not need tmp afterwards, you can move from it:
Object object = std::move(tmp);
Both your examples are valid - in 1 const reference refers to a temporary object, but lifetime of this object is prolonged till the reference goes out of scope (see http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/). The second example is obviously valid, and most modern compilers will optimize away additional copying (even better if you use C+11 move semantics) so for practical purposes examples are equivalent (though in 2 additionally you can modify the value).
In C++11, std::string has a move constructor / move assignment operator, hence the code:
string str = test();
will (at worst) have one constructor call and one move assignment call.
Even without move semantics, this will (likely) be optimised away by NRVO (return value optimisation).
Don't be afraid of returning by value, basically.
Edit: Just to make it 100% clear what is going on:
#include <iostream>
#include <string>
class object
{
std::string s;
public:
object(const char* c)
: s(c)
{
std::cout << "Constructor\n";
}
~object()
{
std::cout << "Destructor\n";
}
object(const object& rhs)
: s(rhs.s)
{
std::cout << "Copy Constructor\n";
}
object& operator=(const object& rhs)
{
std::cout << "Copy Assignment\n";
s = rhs.s;
return *this;
}
object& operator=(object&& rhs)
{
std::cout << "Move Assignment\n";
s = std::move(rhs.s);
return *this;
}
object(object&& rhs)
: s(std::move(rhs.s))
{
std::cout << "Move Constructor\n";
}
};
object test()
{
object o("123");
return o;
}
int main()
{
object o = test();
//const object& o = test();
}
You can see that there is 1 constructor call and 1 destructor call for each - NRVO kicks in here (as expected) eliding the copy/move.
Code 1 is correct. As I said, the C++ Standard guarantees a temporary to a const reference is valid. It's main usage is polymorphic behavior with refenences:
#include <iostream>
class Base { public: virtual void Do() const { std::cout << "Base"; } };
class Derived : public Base { public: virtual void Do() const { std::cout << "Derived"; } };
Derived Factory() { return Derived(); }
int main(int argc, char **argv)
{
const Base &ref = Factory();
ref.Do();
return 0;
}
This will return "Derived". A famouse example was Andrei Alexandrescu's ScopeGuard but with C++11 it's even simpler yet.

Guaranteed lifetime of temporary in C++?

Does C++ provide a guarantee for the lifetime of a temporary variable that is created within a function call but not used as a parameter? Here's an example class:
class StringBuffer
{
public:
StringBuffer(std::string & str) : m_str(str)
{
m_buffer.push_back(0);
}
~StringBuffer()
{
m_str = &m_buffer[0];
}
char * Size(int maxlength)
{
m_buffer.resize(maxlength + 1, 0);
return &m_buffer[0];
}
private:
std::string & m_str;
std::vector<char> m_buffer;
};
And here's how you would use it:
// this is from a crusty old API that can't be changed
void GetString(char * str, int maxlength);
std::string mystring;
GetString(StringBuffer(mystring).Size(MAXLEN), MAXLEN);
When will the destructor for the temporary StringBuffer object get called? Is it:
Before the call to GetString?
After GetString returns?
Compiler dependent?
I know that C++ guarantees that a local temporary variable will be valid as long as there's a reference to it - does this apply to parent objects when there's a reference to a member variable?
Thanks.
The destructor for that sort of temporaries is called at the end of the full-expression. That's the most outer expression which is not part of any other expression. That is in your case after the function returns and the value is evaluated. So, it will work all nice.
It's in fact what makes expression templates work: They can keep hold references to that sort of temporaries in an expression like
e = a + b * c / d
Because every temporary will last until the expression
x = y
Is evaluated completely. It's quite concisely described in 12.2 Temporary objects in the Standard.
litb's answer is accurate. The lifetime of the temporary object (also known as an rvalue) is tied to the expression and the destructor for the temporary object is called at the end of the full expression and when the destructor on StringBuffer is called, the destructor on m_buffer will also be called, but not the destructor on m_str since it is a reference.
Note that C++0x changes things just a little bit because it adds rvalue references and move semantics. Essentially by using an rvalue reference parameter (notated with &&) I can 'move' the rvalue into the function (instead of copying it) and the lifetime of the rvalue can be bound to the object it moves into, not the expression. There is a really good blog post from the MSVC team on that walks through this in great detail and I encourage folks to read it.
The pedagogical example for moving rvalue's is temporary strings and I'll show assignment in a constructor. If I have a class MyType that contains a string member variable, it can be initialized with an rvalue in the constructor like so:
class MyType{
const std::string m_name;
public:
MyType(const std::string&& name):m_name(name){};
}
This is nice because when I declare an instance of this class with a temporary object:
void foo(){
MyType instance("hello");
}
what happens is that we avoid copying and destroying the temporary object and "hello" is placed directly inside the owning class instance's member variable. If the object is heavier weight than a 'string' then the extra copy and destructor call can be significant.
After the call to GetString returns.
I wrote almost exactly the same class:
template <class C>
class _StringBuffer
{
typename std::basic_string<C> &m_str;
typename std::vector<C> m_buffer;
public:
_StringBuffer(std::basic_string<C> &str, size_t nSize)
: m_str(str), m_buffer(nSize + 1) { get()[nSize] = (C)0; }
~_StringBuffer()
{ commit(); }
C *get()
{ return &(m_buffer[0]); }
operator C *()
{ return get(); }
void commit()
{
if (m_buffer.size() != 0)
{
size_t l = std::char_traits<C>::length(get());
m_str.assign(get(), l);
m_buffer.resize(0);
}
}
void abort()
{ m_buffer.resize(0); }
};
template <class C>
inline _StringBuffer<C> StringBuffer(typename std::basic_string<C> &str, size_t nSize)
{ return _StringBuffer<C>(str, nSize); }
Prior to the standard each compiler did it differently. I believe the old Annotated Reference Manual for C++ specified that temporaries should clean up at the end of the scope, so some compilers did that. As late as 2003, I found that behaviour still existed by default on Sun's Forte C++ compiler, so StringBuffer didn't work. But I'd be astonished if any current compiler was still that broken.
StringBuffer is in the scope of GetString. It should get destroyed at the end of GetString's scope (ie when it returns). Also, I don't believe that C++ will guarantees that a variable will exist as long as there is reference.
The following ought to compile:
Object* obj = new Object;
Object& ref = &(*obj);
delete obj;
From cppreference:
All temporary objects are destroyed as the last step in evaluating the
full-expression that (lexically) contains the point where they were
created, and if multiple temporary objects were created, they are
destroyed in the order opposite to the order of creation. This is true
even if that evaluation ends in throwing an exception.