2014-10-22 108 views
1

采取“偷懒”的构造函数,可能有以下接口:延迟初始化与转发

template<class T> 
struct LazyConstruct { 
    // accept any number of arguments, 
    // which would later be used to construct T 
    template<class... U> 
    LazyConstruct(U&&... u) { 
     // store the arguments somehow 
    } 
    T& get() { 
     if(!data) data.reset(new T(/* unpack the arguments */)); 
     return *data; 
    } 
private: 
    std::unique_ptr<T> data; 
}; 

什么是实现这个的好方法?

+0

你允许假设传递的左值的引用将是有效的,当'GET'叫? – Pradhan 2014-10-22 03:48:45

+0

是的。或者更具体地说,如果不是,则行为是未定义的。 (就像在任何其他情况下的ref绑定) – Nick 2014-10-22 12:58:13

回答

1

这里有一点点复杂的做你想做的事情。基本想法是让LazyConstruct将参数包存储在tuple中,然后根据需要解压tuple以构建T

template<class T, class... Args> 
struct LazyConstruct { 
    // accept any number of arguments, 
    // which would later be used to construct T 
    template<class... U> 
    LazyConstruct(U&&... u) 
    : args(std::make_tuple(std::forward<U>(u)...)) 
    { 
    } 

    T& get() { 
     if(!data) data = create(std::index_sequence_for<Args...>()); 
     return *data; 
    } 

    template<std::size_t... I> 
    std::unique_ptr<T> create(std::index_sequence<I...>) 
    { 
     return std::unique_ptr<T>{new T(std::get<I>(args)...)}; 
    } 

private: 
    std::tuple<typename std::decay<Args>::type...> args; 
    std::unique_ptr<T> data; 
}; 

我正在使用C++ 14的std::index_sequence,如果你的标准库实现不出货这一点,那么有上显示它是如何实现的SO(thisthis)的几个例子。

最后一个辅助函数模板构建LazyConstruct实例

template<class T, class... Args> 
LazyConstruct<T, Args...> make_LazyConstruct(Args&&... args) 
{ 
    return LazyConstruct<T, Args...>{std::forward<Args>(args)...}; 
} 

Live demo


基于Alf's answer另一个版本,它使用std::function,使LazyConstruct的类型不更改基于T“的构造函数签名。

template<class T> 
struct LazyConstruct { 
    template<class... Args> 
    LazyConstruct(Args&&... args) 
    : holder([this, args = std::make_tuple(std::forward<Args>(args)...)]() { 
      return create(std::index_sequence_for<Args...>(), std::move(args)); 
     }) 
    { 
    } 

    T& get() { 
     if(!data) data = holder(); 
     return *data; 
    } 

    template<std::size_t... I, class Tuple> 
    std::unique_ptr<T> create(std::index_sequence<I...>, Tuple args) 
    { 
     return std::unique_ptr<T>{new T(std::get<I>(args)...)}; 
    } 

private: 
    std::function<std::unique_ptr<T>()> holder; 
    std::unique_ptr<T> data; 
}; 

Live demo

+0

对于同一'T'但不同构造函数的OP的指定接口'LazyConstruct'实例,将具有相同的类型。这个答案的基本实现如何实现? – 2014-10-22 07:51:49

+0

原来的界面显然不够用,谢谢指出。 @Praetorian,酷! – Nick 2014-10-22 12:56:19

+0

@Nick很高兴帮助。阿尔夫确实有一个很好的观点,在发布答案时我错过了。如果你按照他的回答所示的某种类型的擦除,可以让'LazyConstruct'类型给定不同的'T'构造函数。我已经发布了另一个与他相似的例子。我使用C++ 14 init-capture来完善将参数转发给lambda。 – Praetorian 2014-10-22 15:19:31

1

我不确定你的问题,但对于懒惰的初始化,我建议你沿着boost::optional<T>的方向使用一些东西。你可以延迟初始化,你不会使用指针和堆内存。

class MyClass { 
public: 
    void f(); 
}; 

void anotherFunc(MyClass & c); 

boost::optional<MyClass> mc; //Not initialized, empty, stack memory. 

mc = MyClass{}; 
if (mc != boost::none) 
    mc->f(); 
anotherFunc(*mc); 

文档是在这里:Boost.Optional

+1

请你给一个具体的例子。 – 2014-10-22 04:53:37

+0

谢谢你的例子!如何捕获一次指定的参数并稍后使用这些参数进行构建? – 2014-10-22 05:03:12

+0

为此,您将需要更多,如果您想捕获参数,我建议您使用lambda,稍后调用它并返回对象,例如。 – 2014-10-22 07:04:47

1

最简单的可能是刚刚拍摄参数的拉姆达。

template<class T> 
struct LazyConstruct { 
    // accept any number of arguments, 
    // which would later be used to construct T 
    template<class... U> 
    LazyConstruct(U&&... u) 
     : create([=]() -> T* { return new T(u...); }) 
    {} 
    T& get() { 
     if(!data) data.reset(data.reset(create())); 
     return *data; 
    } 
private: 
    std::unique_ptr<T> data; 
    std::function<auto()->T*> create; 
}; 

声明:编码器未触及的代码。

注意:尽管我现在无法准确地说出这个想法到底出了什么问题(这很晚了),但懒惰的创作不知道是否正确。我怀疑过早的优化。

+0

我喜欢这个类型的删除。但是lambda捕获工作是这样吗?我的意思是不是[=]要复制一切而不是转发? – Nick 2014-10-22 13:17:49

+0

@尼克:这是要复制的论点,是的。如果可以保证所有实际参数的生命周期超出get的第一个调用范围,那么逻辑前向只是安全的。 – 2014-10-22 13:21:21

+0

对于任何lambda或其他引用绑定上下文,“安全”要求是相同的;这里也不错(这是一个最初未能强调的细节)。即没有必要是一个“保证”本身,只是一个合同。 – Nick 2014-10-22 14:09:50

0

按照之前的评论。你想延迟并捕捉这些论据。

编辑:广义的解决方案,应该在C++ 11中工作。警告:未经测试。应用功能留作练习。一个可能的实现见here

template <class T> 
struct make { 
    template <class...Args> 
    T operator()(Args &&... args) const { 
     return T(std::forward<Args>(args)...); 
    } 
}; 


template <class T, class... Args> 
struct object_builder { 

    object_builder(Args... && args) : 
     captured_args_(std::forward<Args>(args)...) {} 

    T operator()() const { 
     return apply(make<T>{}, 
       captured_args_); 
} 

private: 
    std::tuple<Args...> captured_args_; 
}; 


template <class T, class...Args> 
object_builder<T, Args...> make_object_builder(Args &&...args) { 
    return object_builder<T, Args...>(std::forward<Args>(args)...); 
} 

int main() { 
    //Create builders with captured arguments 
    auto scary_monster_builder = 
     make_object_builder<Monster>(scary, "big orc"); 
    auto easy_monster_builder = make_object_builder<Monster>(easy, 
                 "small orc"); 

    //Instantiate objects with the captured arguments from before 
    auto a_scary_monster = scary_monster_builder(); 
    auto an_easy_monster = easy_monster_builder(); 
}