Должен ли я использовать идиому инициализации по требованию, и если да, то как?

У меня есть следующий код:

 MyType x = do_something_dangerous();
// ...
if (some_condition) {
// ...
bar(x);
}
else {
// ...
}
// ...
if (another_condition_which_may_depend_on_previous_if_else} {
// ...
baz(x);
}

Идея состоит в том, что в некоторых случаях, которые, возможно, трудно / неудобно определить заранее, мне нужно использовать x, Но в тех случаях, когда мне не нужно его использовать, попытка его инициализации может быть плохой (скажем, может привести к сбою моего процесса).

Теперь, кажется, что мне нужно использовать это инициализация по требованию владельца (ссылка фокусируется на Java, так что вот набросок): Wrapper<MyType> или же Wrapper<MyType, do_something_dangerous> с get() метод, такой, что первый get() звонки do_something_dangerous() и позже get()Просто передайте значение первого полученного вызова.

  • Это действительно подходящий подход здесь?
  • Есть ли стандартная (ish) реализация этой идиомы или ее вариант?

Заметки:

  • Я мог бы использовать boost::optional, но это было бы немного громоздко, а также искажать предполагаемое использование: «Рекомендуется использовать optional<T> в ситуациях, когда существует ровно одна, понятная (для всех сторон) причина отсутствия значения типа Tи где недостаток стоимости столь же естественен, как наличие какой-либо регулярной стоимости T«.

9

Решение

ИМХО, предлагаемое вами решение идеально подходит:

  • это тривиально реализовать
  • это полностью соответствует вашим потребностям

Конечно, если вы уже используете boost в своем приложении, вы можете посмотреть на предложенный модуль boost :: опциональный, но я не уверен, что это именно то, что вам нужно, так как он больше предназначен для объектов, допускающих обнуляемость, когда то, что вам нужно, игнорируется. инициализация.

Мой совет здесь: придерживайтесь специальной реализации оболочки.

В отличие от других ответов, я думаю, вы должны не использовать синглтон, но просто что-то вроде (реализация с использованием параметра инициализации):

template<typename T, typename I>
class LazyInit {
T* val;
I init;

public:
LazyInit<T, I>(I init): init(init) {
val = NULL;
}
~LazyInit<T, I>() {
delete val;
val = NULL; // normally useless here
}
T& get() {
if (val == NULL) {
val = new T(init);
}
return *val;
}
};

А вот реализация, использующая функцию инициализации:

template<typename T>
class LazyInit {
T* val;
T (*init)();

public:
LazyInit<T>(T (*init)()): init(init) {
val = NULL;
}
~LazyInit<T>() {
delete val;
val = NULL; // normally useless here
}
T& get() {
if (val == NULL) {
val = new T(init());
}
return *val;
}
};
...
LazyInit<MyType> x(do_something);
...
bar(x.get()); // initialization done only here

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

5

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

Если у вас есть C ++ 11, вы можете использовать комбинацию std::async а также std::shared_future:

#include <iostream>
#include <future>

using namespace std;

struct LazyObj {
LazyObj() {
cout << "LazyObj init" << endl;
}
void test() {
cout << "LazyObj test" << endl;
}
};

int main() {
auto x = std::async(launch::deferred, []() -> LazyObj& {
static LazyObj a;
return a;
}).share();

int cond=0;
if( cond == 0 ) {
x.get().test();
x.get().test();
}
return 0;
}

В этом случае использования launch::deferred точки std::async не создавать поток выполнения и вызывать лямбду по требованию, когда get() называется. Чтобы разрешить несколько звонков get() метод, мы конвертируем std::future в std::shared_future с помощью share() метод. Теперь мы можем получить объект lazy-initialisaion, когда или где это необходимо.

2

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