Я немного поиграл с этой темой и придумал альтернативное решение, если вы используете C ++ 11. Учтите следующее:
class MyClass
{
public:
MyClass() :
expensiveObjectLazyAccess()
{
// Set initial behavior to initialize the expensive object when called.
expensiveObjectLazyAccess = [this]()
{
// Consider wrapping result in a shared_ptr if this is the owner of the expensive object.
auto result = std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
// Maintain a local copy of the captured variable.
auto self = this;
// overwrite itself to a function which just returns the already initialized expensive object
// Note that all the captures of the lambda will be invalidated after this point, accessing them
// would result in undefined behavior. If the captured variables are needed after this they can be
// copied to local variable beforehand (i.e. self).
expensiveObjectLazyAccess = [result]() { return result.get(); };
// Initialization is done, call self again. I'm calling self->GetExpensiveObject() just to
// illustrate that it's safe to call method on local copy of this. Using this->GetExpensiveObject()
// would be undefined behavior since the reassignment above destroys the lambda captured
// variables. Alternatively I could just use:
// return result.get();
return self->GetExpensiveObject();
};
}
ExpensiveType* GetExpensiveObject() const
{
// Forward call to member function
return expensiveObjectLazyAccess();
}
private:
// hold a function returning the value instead of the value itself
std::function<ExpensiveType*()> expensiveObjectLazyAccess;
};
Основная идея состоит в том, чтобы содержать функцию, возвращающую дорогой объект в качестве члена, а не сам объект. В конструкторе инициализируйте с помощью функции, которая делает следующее:
- Инициализирует дорогой объект
- Заменяет себя функцией, которая захватывает уже инициализированный объект и просто возвращает его.
- Возвращает объект.
Что мне нравится в этом, так это то, что код инициализации все еще пишется в конструкторе (куда я естественно поместил бы его, если бы лень не требовалась), даже если он будет выполняться только тогда, когда произойдет первый запрос для дорогого объекта.
Недостатком этого подхода является то, что std :: function переназначает себя в своем исполнении. Доступ к любым нестатическим элементам (захватывает в случае использования лямбды) после переназначения приведет к неопределенному поведению, поэтому это требует дополнительного внимания. Кроме того, это своего рода хак, так как GetExoyObject () является const, но он все равно изменяет атрибут члена при первом вызове.
В производственном коде я, вероятно, предпочел бы просто сделать член изменяемым, как описано Джеймс Керран . Таким образом, общедоступный API вашего класса четко заявляет, что член не считается частью состояния объектов, поэтому он не влияет на константность.
Поразмыслив немного, я решил, что std :: async с std :: launch :: deferred также можно использовать в сочетании с std :: shared_future, чтобы иметь возможность получать результат несколько раз. Вот код:
class MyClass
{
public:
MyClass() :
deferredObj()
{
deferredObj = std::async(std::launch::deferred, []()
{
return std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
});
}
const ExpensiveType* GetExpensiveObject() const
{
return deferredObj.get().get();
}
private:
std::shared_future<std::shared_ptr<ExpensiveType>> deferredObj;
};