【问题标题】:Should I use the Initialize-on-demand idiom, and if so how?我应该使用 Initialize-on-demand 习语吗?如果可以,如何使用?
【发布时间】:2015-06-29 17:06:30
【问题描述】:

我有以下代码:

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

这个想法是,在某些情况下,可能很难/不方便提前确定,我需要使用x。但在我不需要使用它的情况下,尝试初始化它可能会很糟糕(比如说,可能会使我的进程崩溃)。

现在,看来我需要使用的是initialize-on-demand holder(链接侧重于Java,所以这是一个草图):某种Wrapper<MyType>Wrapper<MyType, do_something_dangerous>get() 方法,例如第一个 get() 调用 do_something_dangerous() 和后来的 get()s 只是传递第一个调用获得的值。

  • 这确实是一种合适的方法吗?
  • 这个成语是否有一些标准(ish)实现,或者它的变体?

注意事项:

  • 我可以使用boost::optional,但这会有点麻烦并且还会扭曲预期用途:“建议在只有一个明确(对所有各方)理由的情况下使用optional<T>没有 T 类型的值,并且缺少值与具有任何常规值 T 一样自然。"

【问题讨论】:

  • 也许是boost::optionalstd::experimental::optional
  • 我认为您正在寻找的术语是记忆,这是另一个可能有帮助的问题:stackoverflow.com/questions/17805969/…
  • 我不太清楚所需的工作流程:什么是危险的:调用do_something_dangerous(),如果该函数失败,则初始化x,如果该函数使用x失败的。 x 是否可以默认初始化(或等效的东西)以使其无害且可检测?

标签: c++ idioms


【解决方案1】:

恕我直言,您提出的解决方案非常合适:

  • 实现起来很简单
  • 完全满足您的需求

当然,如果您已经在应用程序中使用了 boost,您可以查看建议的 boost::optional 模块,但我不确定它是否正是您想要的,因为它更多地是为可空对象设计的需要延迟初始化。

我的建议是:坚持使用专用的包装器实现。

与其他答案不同,我认为您应该使用单例,而只是使用类似(使用初始化参数实现):

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

您可以轻松地将两者结合起来,使用带参数的函数构建实现。

【讨论】:

  • 查看关于 boost::optional 的编辑,就像你说的那样。另外,在标准库或 Boost 中没有这样的东西吗?最后,您假设我使用的是默认 ctor; @TartanLama 的课程不会更好吗?
  • 这种方法可能更适合您,因为函数静态解决方案将为单个模板实例的所有实例维护一个实例。
  • 我在这里要做的不是T* 我会使用boost::optional 作为成员,因为这样你(a)避免动态内存分配和(b)d'tor 来了免费。
  • 我可能还会模板化采用 init 函数的版本,以便可以传递任何可调用对象,而不仅仅是原始函数指针。
  • @MartinBa :你是正确的模板化,我必须承认我已经达到了我在模板中使用函数类型的限制......随时在这里提出编辑或添加你自己的回答。关于 boost,由于我主要将 C 和 C++ 用于低级代码或需要优化的大量使用例程,因此我很少使用它。
【解决方案2】:

如果你有 c++11,你可以使用 std::asyncstd::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() 时按需调用lambda。为了允许多次调用get() 方法,我们使用share() 方法将std::future 转换为std::shared_future。现在我们可以在需要的时间或地点获取惰性初始化对象。

【讨论】:

  • 在您的示例中,“危险”部分正在构造 LazyObj a。现在,是什么向我保证这会在我期望的时候发生?即第一次调用 lambda 时?
  • @einpoklum 是的,当它第一次调用时。您可以通过设置cond=1 来测试它
猜你喜欢
  • 2021-10-22
  • 1970-01-01
  • 2015-05-17
  • 2011-07-04
  • 1970-01-01
  • 2019-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多