109

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.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • why not inherit and overload or make a global function? i would be cleaner and You wouldn't have to create a class ony to call a member. – Jacek Ławrynowicz Mar 11 '09 at 08:25
  • 1
    If you are going to use this, you should call `m_str.reserve(maxlength)` in `char * Size(int maxlength)`, otherwise the destructor could throw. – Mankarse Mar 18 '12 at 02:22

6 Answers6

118

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • 3
    I never quite got around to getting a copy of the standard. I should make it a priority. – Mark Ransom Feb 25 '09 at 06:11
  • 3
    @JohannesSchaub: What is a "full-expression" in this case: `printf("%s", strdup(std::string("$$$").c_str()) );` ?I mean if `strdup(std::string("$$$").c_str())` is taken as the full expression, then the pointer that `strdup` sees is *valid*. If `std::string("$$$").c_str()` is a full expression, then the pointer that `strdup` sees is *invalid*! Could you please explain a bit more based on this example? – Grim Fandango Dec 09 '14 at 13:00
  • 3
    @GrimFandango AIUI your entire `printf` is the full expression. Thus the `strdup` is an unnecessary memory leak -- you can just let it print the `c_str()` directly. – Josh Stone Nov 10 '15 at 21:22
  • 2
    "That sort of temporaries" -- What sort is that? How can I tell if my temporary is "that sort" of temporary? – R.M. Apr 26 '19 at 20:08
  • 1
    @R.M fair enough. I meant "ones that you create within a function argument", as is done in the question. – Johannes Schaub - litb Apr 26 '19 at 20:35
20

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.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
Rick
  • 3,285
  • 17
  • 17
  • 4
    To make the move work I think you need to drop the const and use std::move like MyType(std::string&& name) : m_name(std::move(name)) {} – gast128 Nov 28 '19 at 17:09
  • This could be the blog post: https://devblogs.microsoft.com/cppblog/rvalue-references-c0x-features-in-vc10-part-2/ – Hari Feb 28 '23 at 15:47
4

After the call to GetString returns.

David Segonds
  • 83,345
  • 10
  • 45
  • 66
4

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.

Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • Spooky how similar they are! Thanks for the warning - the first place I'll try it is VC++6, which is not known for its standards compliance. I'll be watching carefully. – Mark Ransom Feb 25 '09 at 21:53
  • I would have written the class originally on VC++6 so it shouldn't be a problem. – Daniel Earwicker Feb 26 '09 at 16:43
3

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;
BigSandwich
  • 2,768
  • 2
  • 22
  • 26
  • I think I overstated the guarantee - it's for local temporaries only. But it does exist. – Mark Ransom Feb 25 '09 at 05:55
  • I've edited the question. Based on the answers supplied thus far, it seems to be a moot point however. – Mark Ransom Feb 25 '09 at 05:58
  • I still don't think your edit is correct: Object& obj = GetObj(); Object& GetObj() { return &Object(); } //bad - will leave dangling reference. – BigSandwich Feb 25 '09 at 06:26
  • In your example, &Object() ceases to be local after the return, so it's no longer a local temporary - the guarantee doesn't apply anymore, and the reference dangles as you said. – Mark Ransom Feb 25 '09 at 17:41
  • That's circular reasoning, but I feel like this discussion isn't really that important, so I'm not going to argue it. – BigSandwich Feb 25 '09 at 22:06
  • 1
    I'm obviously doing a bad job of explaining myself, and I might not understand 100% either. Look at http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=198 - it explains and answers my original question as well. – Mark Ransom Feb 26 '09 at 14:37
  • 1
    Thanks, for the link, that makes sense now. – BigSandwich Feb 27 '09 at 01:32
3

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.

mada
  • 1,646
  • 1
  • 15