Должна ли эта временная привязка C++ к ссылочному члену быть незаконной?
Мой вопрос (который последует после этого, извините за длинное вступление, вопрос выделен жирным шрифтом) первоначально был вдохновлен пунктом 23 в " Исключительном С ++", посвященном Herb Sutters, где мы находим нечто подобное:
<Вырезано>
...
int main()
{
GenericTableAlgorithm a( "Customer", MyWorker() );
a.Process();
}
с
class GenericTableAlgorithm
{
public:
GenericTableAlgorithm( const string& table,
GTAClient& worker );
bool Process();
private:
struct GenericTableAlgorithmImpl* pimpl_; //implementation
};
class GTAClient
{
///...
virtual bool ProcessRow( const PrimaryKey& ) =0;
//...
};
class MyWorker : public GTAClient
{
// ... override Filter() and ProcessRow() to
// implement a specific operation ...
};
Вырезано>
Теперь у меня есть следующие проблемы с этим кодом (и нет, я ни в коем случае не сомневаюсь в мастерстве г-на Саттера как эксперта по C++):
- Подобный пример не будет работать, так как GTAClient& worker - это неконстантная ссылка, которая не может быть временной, но, возможно, она написана не по стандарту или опечатка, что угодно, это не моя точка зрения.
- Что меня удивляет, так это то, что он собирается делать со ссылкой на работника, даже если Проблема 1 игнорируется.
Очевидно, намерение состоит в том, чтобыMyWorker
используется в NVIGenericTableAlgorithm
доступ кGTAClient
(полиморфный) интерфейс; это исключает, что реализация владеет (значением) членом типаGTAClient
, поскольку это может привести к нарезке и т. д. семантика значений плохо сочетается с полиморфизмом.
У него не может быть члена данных типаMyWorker
либо так как этот класс неизвестенGenericTableAlgorithm
,
Итак, я пришел к выводу, что он должен был использоваться через указатель или ссылку, сохраняя исходный объект и полиморфную природу. - С указателями на временные объекты (
MyWorker()
) редко бывают хорошей идеей, я предполагаю, что авторский план состоял в том, чтобы использовать увеличенный срок службы временных файлов, привязанных к (const) ссылкам, и хранить такую ссылку в объекте.pimpl_
указывает на и использовать его оттуда. (Примечание: в GTAClient также нет функции-клона-члена, которая могла бы выполнить эту работу; давайте не будем предполагать, что в фоновом режиме скрывается фабрика на основе RTTI-typeinfo.)
И вот (наконец-то!) У меня возникает вопрос: (Как) можно ли сделать законным передачу временного элемента-члена класса с увеличенным сроком службы?
Стандарт в §12.2.5(версия C++0x, но то же самое в C++, не знает номер главы) делает следующее исключение из продления времени жизни: "-Временная привязка к элементу ссылки в конструкторе ctor-initializer (12.6.2) сохраняется до выхода из конструктора."
Поэтому объект нельзя использовать при вызове клиентского кода a.Process(); потому что ссылка временно от MyWorker()
уже мертв!
Теперь рассмотрим пример моего собственного крафта, который демонстрирует проблему (протестировано на GCC4.2):
#include <iostream>
using std::cout;
using std::endl;
struct oogie {
~oogie()
{
cout << "~oogie():" << this << ":" << m_i << endl;
}
oogie(int i_)
: m_i(i_)
{
cout << "oogie():" << this << ":" << m_i << endl;
}
void call() const
{
cout << "call(): " << this << ":" << m_i << endl;
}
int m_i;
};
oogie func(int i_=100)
{
return oogie(i_);
}
struct kangoo
{
kangoo(const oogie& o_)
: m_o(o_)
{
}
const oogie& m_o;
};
int main(int c_, char ** v_)
{
//works as intended
const oogie& ref = func(400);
//kablewy machine
kangoo s(func(1000));
cout << ref.m_i << endl;
//kangoo's referenced oogie is already gone
cout << s.m_o.m_i << endl;
//OK, ref still alive
ref.call();
//call on invalid object
s.m_o.call();
return 0;
}
который производит вывод
Oogie():0x7fff5fbff780:400 уги (): 0x7fff5fbff770: 1000 ~ уги (): 0x7fff5fbff770: 1000 400 1000 call (): 0x7fff5fbff780: 400 вызов (): 0x7fff5fbff770:1000 ~ Oogie():0x7fff5fbff780:400
Вы можете видеть, что в случае const oogie& ref значение немедленного временного возврата привязки к ссылке у func() имеет увеличенное время жизни указанной ссылки (до конца main), так что все в порядке.
НО: 1000-уги объект уже уничтожен сразу после того, как kangoo-ы были построены. Код работает, но мы имеем дело с нежитью здесь...
Итак, чтобы поставить вопрос еще раз:
Во-первых, я что-то здесь упускаю, а код правильный / законный?,
Во-вторых, почему GCC не предупреждает меня об этом, даже с указанием -Wall? Должно ли это? Может ли это?
Спасибо за ваше время,
Мартин
4 ответа
Я думаю, что это сложная часть, которая не слишком ясна. Был такой же вопрос всего пару дней назад.
По умолчанию временные фильтры уничтожаются в порядке, обратном построению, когда полное выражение, в котором они были созданы, завершается. До сих пор все хорошо и понятно, но тогда возникают исключения (12.2 [class.tegoti]/4,5), и все становится запутанным.
Вместо того, чтобы иметь дело с точной формулировкой и определениями в стандарте, я подойду к проблеме с точки зрения разработки / компилятора. Временные создаются в стеке, когда функция завершает стек, освобождается фрейм (указатель стека перемещается обратно в исходное положение до начала вызова функции).
Это подразумевает, что временный объект никогда не сможет выжить после функции, в которой он был создан. Точнее, он не может пережить область, в которой он был определен, даже если он может фактически пережить полное выражение, в котором он был создан.
Ни одно из исключений в стандарте не выходит за рамки этого ограничения, во всех случаях срок службы временного объекта продлевается до точки, которая гарантированно не превысит вызов функции, в которой был создан временный объект.
Оригинальная статья Гуру Недели находится здесь: Гуру Недели # 15.
Часть вашего ответа может исходить от самого Херба, в "Кандидате на самый важный const" - "const" часть назначения временного ссылки действительно важна.
Так что это выглядит так, как будто это ошибка в оригинальной статье.
Я всегда считал, что передача параметров адреса (будь то по ссылке или по указателю) требует понимания времени жизни передаваемой вещи. Период. Конец обсуждения. Кажется, это просто атрибут среды без сбора мусора / НЕ-управляемой ссылки.
Это одно из преимуществ GC.
В C++ иногда проблемы жизни решались с помощью:
X:: клон
или явной документацией интерфейса, например:
"это зависит от экземпляра Y, чтобы гарантировать, что экземпляр X, переданный в параметре x, существует в течение всего времени жизни Y"
или
явное копирование х в кишках получателя.
Это просто с ++. То, что C++ добавил гарантию на константные ссылки, было хорошо (и действительно несколько необходимо), но это было так.
Таким образом, я считаю код, показанный в вопросе, неверным и придерживаюсь мнения, что это должно было быть:
int main()
{
MyWorker w;
GenericTableAlgorithm a( "Customer", w);
a.Process();
}
Я не думаю, что вы можете сделать это с нынешним C++. Вам нужна семантика перемещения, которая будет представлена в C++x0.