Я пишу класс C
с членом foo
типа foo_t
, Этот член должен быть определен и действителен в течение всей жизни C
пример; однако у меня нет необходимой информации для ее построения во время компиляции, т.е. я не могу использовать
class C {
foo_t foo { arg };
}
И при этом я не могу построить это, когда C
вызывается, то есть я не могу иметь
class C {
foo_t foo;
C (whatever) : foo(compute_arg(whatever)) { }
}
скорее я могу построить его только после некоторого кода в C
Ктор побежал. Редактировать: Причиной этого может быть то, что мне нужно запустить некоторый код с побочными эффектами (например, дисковый или сетевой ввод / вывод), чтобы получить аргумент конструкции; и мне нужно, чтобы этот код выполнялся также для возможности инициализации других членов, поэтому я не могу просто вызвать его несколько раз как свободную функцию из списка инициализации.
Итак, как я должен представлять foo
?
foo_t
может быть сконструирован по умолчанию с некоторым фиктивным / недействительным / пустым / нулевым значением, тогда я могу позволить этому произойти и быть уверенным в том, что к нему никогда не будет обращаться в этом фиктивном состоянии. Ущерб: декларация foo
в C
не означает, что это всегда действует.Если foo_t
только имеет действительное состояние, то есть я не могу создать его вообще, пока не получу соответствующую информацию, тогда:
std::unique_ptr<foo_t>
; изначально это будет nullptr
, а затем будет назначен. Ущерб: нет признаков того, что он никогда не будет нулевым после C()
заключает; Бесполезное распределение.std::optional<foo_t>
; Изначально это будет nullopt
, а затем будет назначен. Ущерб: нет признаков того, что он никогда не будет пустым после C()
заключает; требует C ++ 14; слово «необязательно» предполагает, что это «необязательно» иметь foo
в то время как это не.Меня больше интересует второй случай, так как в первом случае неоднозначность foo_t
Действительность является своего рода встроенной. Есть ли лучшая альтернатива двум, о которых я говорил?
Примечание: мы не можем изменить foo_t
,
Давайте рассмотрим немного более конкретный случай
struct C {
foo_t foo1;
foo_t foo2;
C () :
foo1(read_from_file()),
foo2(read_from_file()),
{ }
static whatever_t read_from_file();
}
и давайте предположим, что нежелательно считывать одни и те же данные из файла дважды.
Одним из возможных подходов может быть:
struct C {
foo_t foo1;
foo_t foo2;
C(): C{Create()} {}
private:
static C Create()
{
return C{read_from_file()};
}
C(whatever_t whatever):
foo1{whatever},
foo2{whatever}
{}
static whatever_t read_from_file();
}
Спасибо @VittorioRomeo за предложения по его улучшению.
В общем, если вы можете построить foo_t
в телах конструктора некоторого класса (без списков инициализаторов членов) вы можете изменить свой код так, чтобы ваш класс теперь имел foo_t
Атрибут и его конструкторы либо делегируют конструкцию, либо конструируют ее внутри своих списков инициализаторов.
По сути, в большинстве случаев вы можете переписать ваш проблемный конструктор так, чтобы он делегировал другому конструктору, предоставляя ему необходимую информацию для создания foo_t
экземпляр в списке инициализатора члена (который я быстро и неофициально проиллюстрировал в комментариях следующим «примером»). https://ideone.com/ubbbb7 )
В более общем смысле, и если по какой-либо причине конструкция кортежа окажется проблемой, последующее преобразование будет (в общем) работать. По общему признанию, это немного долго (и некрасиво), но имейте в виду, что это ради общности, и что, возможно, можно упростить вещи на практике.
Давайте предположим, что у нас есть конструктор, где мы создаем foo_t
для простоты мы будем далее предполагать, что он имеет следующую форму:
C::C(T1 arg_1, T2 arg_2) {
side_effects(arg_1, arg_2);
TL1 local(arg_1, arg_2);
second_side_effects(arg_1, arg_2, local);
foo_t f(arg_1, arg_2, local); // the actual construction
final_side_effects(arg_1, arg_2, local, f);
}
Где вызовы функции возможно видоизменяют аргументы.
Мы можем делегировать один раз для устранения декларации local_1
в теле конструктора, а затем еще раз, чтобы избавиться от вызова second_side_effects(arg_1, arg_2, local)
,
C::C(T1 arg_1, T2 arg_2)
: C::C(arg_1, arg_2
,([](T1& a, T2& b){
side_effects(a, b);
}(arg_1, arg_2), TL1(a, b))) {}
C::C(T1& arg_1, T2& arg_2, TL1&& local)
: C::C(arg_1, arg_2
,[](T1& a, T2& b, TL1& c) -> TL1& {
second_side_effects(a, b, c);
return c;
}(arg_1, arg_2, local)) {}
C::C(T1& arg_1, T2& arg_2, TL1& local) {
foo_t f(arg_1, arg_2, local); // the actual construction
final_side_effects(arg_1, arg_2, local, f);
}
Очевидно, что f
можно сделать фактическим членом C и создать его в списке инициализации этого последнего конструктора.
Можно обобщить любое количество локальных переменных (и аргументов). Однако я предположил, что у нашего начального конструктора не было списка инициализаторов членов. Если бы он был, нам, возможно, понадобилось бы:
arg_i
перед тем как они были видоизменены и передают копии по цепочке конструктора, чтобы в конечном итоге их можно было использовать для конструирования других членов в списке инициализатора членаПоследний должен быть выбран, если по какой-то причине конструктор члена будет иметь побочные эффекты.
Однако есть случай, когда все это разваливается. Давайте рассмотрим следующий сценарий:
#include <memory>
struct state_t; // non copyable, non movable
// irreversible function that mutates an instance of state_t
state_t& next_state(state_t&);
struct foo_t {
foo_t() = delete;
foo_t(const foo_t&) = delete;
foo_t(const state_t&);
};
// definitions are elsewhere
class C {
public:
struct x_first_tag {};
struct y_first_tag {};
// this constructor prevents us from reordering x and y
C(state_t& s, x_first_tag = {})
: x(new foo_t(s))
, y(next_state(s)) {}
// if x and y were both constructed in the member initializer list
// x would be constructed before y
// but the construction of y requires the original s which will
// be definitively lost when we're ready to construct x !
C(state_t& s, y_first_tag = {})
: x(nullptr)
, y(s) {
next_state(s);
x.reset(new foo_t(s));
}
private:
std::unique_ptr<foo_t> x; // can we make that a foo_t ?
foo_t y;
};
В этой ситуации, по общему признанию, я не знаю, как переписать этот класс, но я считаю, что это достаточно редко, чтобы не иметь большого значения.