Гарантированное время жизни временного в С++?
Предоставляет ли С++ гарантию на время жизни временной переменной, которая создается в вызове функции, но не используется в качестве параметра? Вот пример класса:
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;