Должна ли эта временная привязка 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++):

    1. Подобный пример не будет работать, так как GTAClient& worker - это неконстантная ссылка, которая не может быть временной, но, возможно, она написана не по стандарту или опечатка, что угодно, это не моя точка зрения.
    2. Что меня удивляет, так это то, что он собирается делать со ссылкой на работника, даже если Проблема 1 игнорируется.
      Очевидно, намерение состоит в том, чтобы MyWorker используется в NVI GenericTableAlgorithm доступ к GTAClient (полиморфный) интерфейс; это исключает, что реализация владеет (значением) членом типа GTAClient, поскольку это может привести к нарезке и т. д. семантика значений плохо сочетается с полиморфизмом.
      У него не может быть члена данных типа MyWorker либо так как этот класс неизвестен GenericTableAlgorithm,
      Итак, я пришел к выводу, что он должен был использоваться через указатель или ссылку, сохраняя исходный объект и полиморфную природу.
    3. С указателями на временные объекты (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.

    Другие вопросы по тегам