Гарантированное время жизни временного в С++?

Предоставляет ли С++ гарантию на время жизни временной переменной, которая создается в вызове функции, но не используется в качестве параметра? Вот пример класса:

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;
};

И вот как вы его используете:

// 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);

Когда будет вызван деструктор для временного объекта StringBuffer? Это:

  • Перед вызовом GetString?
  • После возврата GetString?
  • Зависит от компилятора?

Я знаю, что С++ гарантирует, что локальная временная переменная будет действительна до тех пор, пока существует ссылка на нее - применимо ли это к родительским объектам, когда есть ссылка на переменную-член?

Спасибо.

Ответ 1

Деструктор для такого рода временных запросов вызывается в конце полного выражения. Это самое внешнее выражение, которое не является частью какого-либо другого выражения. Это происходит в вашем случае после возвращения функции и вычисления значения. Таким образом, все будет хорошо.

На самом деле то, что заставляет шаблоны выражений работать: они могут удерживать ссылки на подобные типы в выражении типа

e = a + b * c / d

Поскольку каждое временное действие будет продолжаться до выражения

x = y

Исследуется полностью. Это довольно кратко описано в 12.2 Temporary objects в Стандарте.

Ответ 2

litb ответ верен. Время жизни временного объекта (также известного как rvalue) привязывается к выражению, а деструктор для временного объекта вызывается в конце полного выражения и когда вызывается деструктор на StringBuffer, деструктор на m_buffer также будет но не деструктор на m_str, так как он является ссылкой.

Обратите внимание, что С++ 0x немного меняет ситуацию, потому что он добавляет ссылки rvalue и перемещает семантику. По существу, используя ссылочный параметр rvalue (обозначенный & &), я могу "переместить" значение r в функцию (вместо копирования), а время жизни rvalue может быть привязано к объекту, в который он перемещается, а не к выражению, Существует действительно хорошая запись в блоге из команды MSVC, которая подробно описывает это, и я призываю людей читать ее.

Педагогический пример для перемещения rvalue - это временные строки, и я покажу назначение в конструкторе. Если у меня есть класс MyType, который содержит переменную члена строки, его можно инициализировать с помощью rvalue в конструкторе, например:

class MyType{
   const std::string m_name;
public:
   MyType(const std::string&& name):m_name(name){};
}

Это хорошо, потому что, когда я объявляю экземпляр этого класса с временным объектом:

void foo(){
    MyType instance("hello");
}

происходит то, что мы избегаем копирования и уничтожения временного объекта, а "привет" помещается непосредственно внутри переменной члена экземпляра класса владельца. Если объект имеет более тяжелый вес, чем "строка", то дополнительный вызов копии и деструктора может быть значительным.

Ответ 3

После возврата вызова GetString.

Ответ 4

Я написал почти точно такой же класс:

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); }

До стандарта каждый компилятор делал это по-другому. Я полагаю, что в старом аннотированном справочном руководстве для С++ указано, что временные разделы должны очищаться в конце области, поэтому некоторые компиляторы сделали это. Уже в 2003 году я обнаружил, что по-прежнему по-прежнему существовало поведение на компиляторе Sun Forte С++, поэтому StringBuffer не работал. Но я был бы поражен, если бы какой-либо текущий компилятор все еще был сломан.

Ответ 5

StringBuffer входит в область GetString. Он должен быть уничтожен в конце области GetString (т.е. когда он вернется). Кроме того, я не считаю, что С++ гарантирует, что переменная будет существовать до тех пор, пока есть ссылка.

Следующее должно компилироваться:

Object* obj = new Object;
Object& ref = &(*obj);
delete obj;