Представляет значение, которое не может быть инициализировано ctor-initialization-list

Я пишу класс 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,

-3

Решение

Давайте рассмотрим немного более конкретный случай

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 за предложения по его улучшению.

Wandbox

1

Другие решения

В общем, если вы можете построить 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;
};

В этой ситуации, по общему признанию, я не знаю, как переписать этот класс, но я считаю, что это достаточно редко, чтобы не иметь большого значения.

1

По вопросам рекламы [email protected]