2015-12-18 30 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你可以找到一个抽象的访问者的简单的例子,有一个派生的具体状态访问者与无关的混凝土构件的工作)