2015-12-18 59 views
2

我對ood有點新鮮。閱讀GoF的設計模式我找到了Visitor。將std :: function應用於訪問者設計模式

我的版本的訪問者模式比Generic visitor using variadic templates中提到的更具體。所以,我的想法是通過私人的std::function創建具體的訪客,這將在施工期間提供。然後,每個訪問功能將調用相應的私人std::function

我的問題:如上所述是否實施訪問者是好習慣,或者爲什麼?

只有想到的缺點是模棱兩可的,也就是說,很難知道訪問者將在複合材料上做什麼特定實例。

+0

那麼它當然可以工作,但它可能並不清楚什麼每個訪問者的行爲將是。我能有您的實現下參觀不同的兩個同類型的遊客。 – AndyG

+0

我在考慮轉發參數。我看了一場關於斯科特邁耶斯關於轉發的談話,並且非常害怕。這可能會危險嗎? – Incomputable

+0

沒有,轉發你的論點保留L值或它們的R值的性質。如果你從一個你不應該擁有的物體移開,那隻會有危險。 – AndyG

回答

3

訪問者訪問std::function的方式是更改元素的接受部分。作爲代價,你會失去雙重調度,但是你會將迭代的樣板抽象一些。

代替元件上的一個accept方法的,具有每accept探視之一。

如果您想在標準訪問者中以多種方式訪問​​事物,則需要編寫更多訪問者類型,並添加新的重載接受它們。

std::function基於一個,你只要寫一個新的accept型功能用不同的名稱;該名稱以方法的名稱出現,而不是以訪客類型的名稱(因爲訪客類型是匿名的)。

在使用SFINAE std::function智能的C++ 14中,您可以使用一個超載的accept,但是您必須將「訪問標籤」傳遞給訪問者以確定它期望的訪問類型。這可能不值得麻煩。

第二個問題是std::function不支持多重參數類型的重載。訪問者的用途之一是我們根據元素的動態類型進行不同的調度 - 完全雙派遣。


作爲一個具體的案例研究,設想3種訪問:保存,加載和顯示。保存和顯示的主要區別在於顯示器清除不可見的事物(遮擋或設置爲不可見)。

在傳統的元素/訪問者下,您將有一個接受函數帶有3個過載,每個接受函數的取值爲Saver*Loader*Displayer*SaverLoaderDisplayer中的每一個都有一堆visit(element*)visit(derived_element_type*)方法。

std::function訪問下,您的元素改爲save(std::function<void(element*)>load(display(方法。沒有雙重調度完成,因爲std::function只公開一個接口。

現在,我們可以編寫一個std::function -esque多重調度的超載機制,如果我們需要的話。不過這是先進的C++。


template<class Is, size_t I> 
struct add; 
template<class Is, size_t I> 
using add_t=typename add<Is,I>::type; 

template<size_t...Is, size_t I> 
struct add<std::index_sequence<Is...>, I>{ 
    using type=std::index_sequence<(I+Is)...>; 
}; 

template<template<class...>class Z, class Is, class...Ts> 
struct partial_apply; 
template<template<class...>class Z, class Is, class...Ts> 
using partial_apply_t=typename partial_apply<Z,Is,Ts...>::type; 

template<template<class...>class Z, size_t...Is, class...Ts> 
struct partial_apply<Z,std::index_sequence<Is...>, Ts...> { 
    using tup = std::tuple<Ts...>; 
    template<size_t I> using e = std::tuple_element_t<I, tup>; 

    using type=Z< e<Is>... >; 
}; 

template<template<class...>class Z, class...Ts> 
struct split { 
    using left = partial_apply_t<Z, std::make_index_sequence<sizeof...(Ts)/2>, Ts...>; 
    using right = partial_apply_t<Z, add_t< 
    std::make_index_sequence<(1+sizeof...(Ts))/2>, 
    sizeof...(Ts)/2 
    >, Ts...>; 
}; 
template<template<class...>class Z, class...Ts> 
using right=typename split<Z,Ts...>::right; 
template<template<class...>class Z, class...Ts> 
using left=typename split<Z,Ts...>::left; 

template<class...Sigs> 
struct functions_impl; 

template<class...Sigs> 
using functions = typename functions_impl<Sigs...>::type; 

template<class...Sigs> 
struct functions_impl: 
    left<functions, Sigs...>, 
    right<functions, Sigs...> 
{ 
    using type=functions_impl; 
    using A = left<functions, Sigs...>; 
    using B = right<functions, Sigs...>; 
    using A::operator(); 
    using B::operator(); 
    template<class F> 
    functions_impl(F&& f): 
    A(f), 
    B(std::forward<F>(f)) 
    {} 
}; 
template<class Sig> 
struct functions_impl<Sig> { 
    using type=std::function<Sig>; 
}; 

,讓你支持多個簽名(但只有一個功能)的std::function。要使用它,你可以試試:

functions< void(int), void(double) > f = [](auto&& x){std::cout << x << '\n'; }; 

,當與int調用,打印一個int,並且當與double調用,打印雙。如上所述,這是高級的C++:我只是簡單地將它包含在內以表明該語言足夠強大,可以處理該問題。

Live example

使用該技術,您可以使用std::function類型接口進行雙重調度。簡單的訪問者必須傳遞一個可調用的函數,可以處理您發送的每個超載,而且您的元素必須詳細說明訪問者在其簽名中支持的所有類型。

你會注意到,如果你實現這個,你會在訪問視域中得到一些非常神奇的多態。你將被動態地調用你正在訪問的東西的靜態類型,你只需要編寫一個方法體。在合同中添加新的需求(在accept方法的接口聲明處),而不是2 + K,如經典訪問(在accept方法中,在訪問類型的接口中以及在各種重載中的訪問類(這可以通過我會承認的CRTP來消除))。

以上functions<Sigs...>存儲該函數的N個副本。一個更優化的可以存儲一次tur函數和N個調用視圖。這是一個更難觸摸,但只是一個觸摸。

template<class...Sigs> 
struct efficient_storage_functions: 
    functions<Sigs...> 
{ 
    std::unique_ptr<void, void(*)(void*)> storage; 
    template<class F> // insert SFINAE here 
    efficient_storage_functions(F&& f): 
    storage{ 
     new std::decay_T<F>(std::forward<F>(f)), 
     [](void* ptr){ 
     delete static_cast<std::decay_t<F>*>(ptr); 
     } 
    }, 
    functions<Sigs...>(
     std::reference_wrapper<std::decay_t<F>>(
     get<std::decay_t<F>>() 
    ) 
    ) 
    {} 
    template<class F> 
    F& get() { 
    return *static_cast<F*>(storage.get()); 
    } 
    template<class F> 
    F const& get() const { 
    return *static_cast<F const*>(storage.get()); 
    } 
}; 

其未來的需求與小目標優化來改善(不存儲在堆棧上的類型)和SFINAE支持,因此它並不試圖從東西是不兼容的構建。

它存儲一個unique_ptr來電可調用的副本,它所繼承的無數std::function從所有存儲std::reverence_wrapper其內容。

還缺少的是複製構造。

1

您在建設中爲您的visitor提供std::function的想法面臨着雙重調度的挑戰:訪問者必須爲其可能訪問的每個具體對象類型實施一個visting函數。

您可以提供一個滿足此挑戰的單個std::function(例如:所有具體元素都是相同基類的派生類)。但這並不總是可能的。

此外,訪客不一定是無國籍的。它可以維護它訪問的evry結構的狀態(例如:維護元素或總計數)。雖然這很容易在訪問者級別編碼,但在std::function中更難以編碼。這意味着您的訪問者實施可能會有一些限制。

因此,我寧願建議與派生訪問者類的工作:這是更具可讀性,作品即使具體內容無關,併爲您提供例如有狀態的遊客更多的靈活性。

(在此other answer你可以找到一個抽象的訪問者的簡單的例子,有一個派生的具體狀態訪問者與無關的混凝土構件的工作)