2014-10-22 104 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(); 
}