создание объекта из временного

Я использую сторонний класс с (только) конструктором следующим образом

class foo  // cannot be altered
{
  public:
    explicit foo(std::istream&);
  ...
};

и документация которого предлагает следующий подход

std::ifstream from("file.txt");
foo obj(from);
from.close();

Я не могу изменить foo и хочу использовать его как член другого класса

class bar
{
     foo obj;                           // must not be altered
   public:
     explicit
     bar(std::string const&filename)    // must not be altered
       : obj(std::ifstream(filename))   // error: no matching constructor
   {}
   ...
};

за исключением того, что это не работает, поскольку временный std::ifstream, созданный из filename, не гарантирует, что он проживет достаточно долго, чтобы построить foo obj, и, следовательно, не может быть преобразован в std::istream& (ситуация была бы другой, если бы foo::foo() принял const std::istream&).

Итак, мой вопрос: могу ли я заставить конструктор bar работать, не меняя дизайн bar (например, bar::bar(), чтобы взять std::istream&, или бар, чтобы удерживать std::unique_ptr<foo> вместо foo, или путем добавления элементов данных в bar)?


person Walter    schedule 20.08.2020    source источник
comment
Попросить bar взять std::istream& и передать obj?   -  person NathanOliver    schedule 20.08.2020
comment
@NathanOliver, который изменил бы дизайн bar нежелательным образом.   -  person Walter    schedule 20.08.2020
comment
Если бы foo::foo принял const istream&, вместо этого у вас было бы неопределенное поведение. По-моему, не улучшение.   -  person molbdnilo    schedule 20.08.2020
comment
@molbdnilo Можете ли вы рассказать об этом подробнее? Я думал, что это четко определенное поведение: временное будет жить до тех пор, пока на него ссылается const, не так ли?   -  person Walter    schedule 20.08.2020
comment
... без изменения дизайна bar вы имеете в виду ... без изменения дизайна foo ?   -  person 463035818_is_not_a_number    schedule 20.08.2020
comment
видимо нет, тогда я не понимаю вопроса :/   -  person 463035818_is_not_a_number    schedule 20.08.2020
comment
@idclev463035818 idclev463035818 Я имел в виду то, что сказал: никаких изменений в foo вообще, никаких изменений в базовом дизайне bar: тот же интерфейс конструктора, те же элементы.   -  person Walter    schedule 20.08.2020
comment
Если вы не хотите, чтобы bar удерживал объект ifstream, вы должны принять его в конструкторе. foo требует lvalue, и единственный способ создать его на лету — использовать new, но если вы не удерживаете указатель, у вас возникает утечка памяти.   -  person NathanOliver    schedule 20.08.2020
comment
считается ли упаковка bar в moo изменением дизайна?   -  person 463035818_is_not_a_number    schedule 20.08.2020
comment
@Walter Они существуют до тех пор, пока первая привязка, которая является параметром конструктора. Если foo не хранит поток, а использует его только для построения, все было бы в порядке - если бы вы могли читать из константного потока, но не можете.   -  person molbdnilo    schedule 20.08.2020
comment
@NathanOliver Что, если у меня есть объект, который хранит mutable std::ifstream и позволяет преобразовывать тип const в std::ifstream& - это нормально?   -  person Walter    schedule 20.08.2020
comment
@Walter Где ты собираешься это хранить? В этот момент у вас должен быть только ifstream член в bar.   -  person NathanOliver    schedule 20.08.2020
comment
@NathanOliver не хранит его нигде, а использует временный объект этого типа и передает его конструктору foo - по крайней мере, компилятор clang больше не выдает ошибок.   -  person Walter    schedule 20.08.2020
comment
@ Уолтер Это не работает. Временное продление срока службы работает только с локальными ссылками на функции. Передача временного объекта конструктору, в котором объект содержит ссылку на него, не продлевает время жизни и оставит вас с оборванной ссылкой.   -  person NathanOliver    schedule 20.08.2020


Ответы (2)


Ваши проектные ограничения невозможно удовлетворить. Безопаснее всего расслабляться, держась за std::ifstream в bar.

class bar
{
     std::ifstream objs_stream;         // must be declared before obj
     foo obj;                           // must not be altered
   public:
     explicit
     bar(std::string const&filename)    // must not be altered
       : objs_stream(filename), obj(objs_stream)
   {}
   ...
};

Другой вариант — отправить патч стороннему классу:

class foo
{
  public:
    explicit foo(std::istream&);
    explicit foo(std::istream&& is) : foo(is) {}
  ...
};

Если foo имеет конструктор копирования или перемещения, вы можете

foo make_foo(const std::string& filename)
{
    std::ifstream is(filename);
    return foo(is);
}

class bar
{
     foo obj;                           // must not be altered
   public:
     explicit
     bar(std::string const&filename)    // must not be altered
       : obj(make_foo(filename))
   {}
   ...
};
person Caleth    schedule 20.08.2020
comment
Извините, но это было явно исключено как решение. - person Walter; 20.08.2020
comment
Ага. Дизайн не может быть выполнен, так что это допустимый обходной путь. - person NathanOliver; 20.08.2020
comment
@Walter никакая другая часть bar не должна взаимодействовать с objs_stream - person Caleth; 20.08.2020
comment
@NathanOliver, пожалуйста, посмотрите на мой альтернативный ответ. Судя по вашему комментарию, это невозможно. - person Walter; 20.08.2020

Я придумал следующую возможность (которая компилируется без ошибок).

class bar
{
    foo obj;                           // must not be altered
    struct tmp {
       mutable std::ifstream s;
       tmp(std::string const&f) : s(f) {}
       operator std::istream&() const { return s; }
    };
  public:
    explicit
    bar(std::string const&filename)    // must not be altered
      : obj(tmp(filename))
  {}
  ...
};

Это нормально? т.е. будет ли tmp::s жить до тех пор, пока не вернется конструкция obj (что, согласно документации foo, см. отредактированный вопрос, достаточно)?

person Walter    schedule 20.08.2020
comment
Это оставит вас с оборванной ссылкой, а использование потока foo будет иметь неопределенное поведение. Компилятору не требуется диагностировать это. - person NathanOliver; 20.08.2020
comment
@NathanOliver, можете ли вы рассказать об этом подробнее? Обратите внимание, что obj не удерживает ссылку/указатель на аргумент своего конструктора (см. отредактированный вопрос). - person Walter; 20.08.2020
comment
Как только obj(tmp(filename)) заканчивается, s из tmp уничтожается, что означает, что ссылка на поток obj ссылается на объект, которого больше не существует. Если obj необходимо использовать поток после его создания, то при использовании будет использоваться висячая ссылка, которая является UB. Если obj использовать поток только в конструкторе, то вы в безопасности, но это требует, чтобы все, кто работает над этим, знали об этом, поэтому это налагает требования к документации. - person NathanOliver; 20.08.2020
comment
@NathanOliver Если obj нужно использовать поток после его создания, это не так. Я надеялся, что это ясно изложено в вопросе. Я знаю, что дизайн foo плохой, но ничего не могу с этим поделать. - person Walter; 20.08.2020