如果我在std :: function中存储多态仿函数,有没有办法在不知道具体类型的情况下提取仿函数?
以下是代码的简化版本:
struct Base {
//...
virtual int operator()(int foo) const = 0;
void setBar(int bar){}
};
struct Derived : Base {
//...
int operator()(int foo) const override {}
};
std::function<int(int)> getFunction() {
return Derived();
}
int main() {
auto f = getFunction();
// How do I call setBar() ?
if (Base* b = f.target<Base>()) {} // Fails: returns nullptr
else if(Derived* d = f.target<Derived>()) {
d->setBar(5); // Works but requires Derived type
}
std::cout << f(7) << std::endl;
return 0;
}
我希望客户端能够提供自己的功能,并且我的处理程序可以使用Base的功能(如果可用)。
回退当然只是使用抽象基类而不是std :: function,客户端将实现ABC接口,因为它们将具有预C ++ 11:
std::shared_ptr<Base> getFunction {
return std::make_shared<Derived>();
}
但我想知道是否可以使用C ++ 14创建更灵活,更易于使用的界面。似乎所有缺少的是std :: function :: target
中的强制转换答案 0 :(得分:0)
似乎缺少的是std :: function :: target
中的强制转换
目前需要的所有target<T>
都是检查target_id<T> == stored_type_info
。
能够在可能看不到该类型的上下文中回送到真实(已擦除)类型,然后检查它与所请求类型的关系......实际上是不可行的。
无论如何,std::function
仅在函数签名上是多态的。这是它抽象的东西。如果您想要通用多态,只需返回unique_ptr<Base>
并使用它。
如果您真的希望function<int(int)>
用于函数调用语法,请使用unique_ptr<Base>
的pimpl包装器对其进行实例化。
答案 1 :(得分:0)
可能的解决方案是使用薄包装器:
struct BaseCaller {
BaseCaller( std::unique_ptr<Base> ptr ) : ptr_( std::move( ptr ) ) {}
int operator()( int foo ) { return (*ptr)( foo ); }
std::unique_ptr<Base> ptr_;
};
现在用户必须创建所有派生自此包装的Base:
std::function<int(int)> getFunction() {
return BaseCaller( std::make_unique<Derived>() );
}
并且您在通话中检查目标是BaseCaller
。
答案 2 :(得分:0)
我希望客户端能够提供自己的功能,并且我的处理程序可以使用Base的功能(如果它可用)。
使用虚拟调度的主要缺点是它可能会创建优化障碍。例如,虚拟函数调用通常不能内联,并且&#34; devirtualization&#34;在实际情况下,编译器实际上很难进行优化。
如果您处于代码性能至关重要的情况,您可以滚动自己的类型擦除并避免任何vtable /动态分配。
我将遵循旧的(但众所周知的)文章"Impossibly Fast Delegates"中展示的模式。
// Represents a pointer to a class implementing your interface
class InterfacePtr {
using ObjectPtr = void*;
using CallOperator_t = int(*)(ObjectPtr, int);
using SetBar_t = void(ObjectPtr, int);
ObjectPtr obj_;
CallOperator_t call_;
SetBar_t set_bar_;
// Ctor takes any type that implements your interface,
// stores pointer to it as void * and lambda functions
// that undo the cast and forward the call
template <typename T>
InterfacePtr(T * t)
: obj_(static_cast<ObjectPtr>(t))
, call_(+[](ObjectPtr ptr, int i) { return (*static_cast<T*>(ptr))(i); })
, set_bar_(+[](ObjectPtr ptr, int i) { static_cast<T*>(ptr)->set_bar(i); })
{}
int operator()(int i) {
return call_(obj_, i);
}
void set_bar()(int i) {
return set_bar_(obj_, i);
}
};
然后,您会在API中使用InterfacePtr
而不是指向Base
的指针。
如果您希望接口成员set_bar
是可选的,那么您可以使用SFINAE来检测是否存在set_bar
,并且具有两个版本的构造函数,一个版本适用于何时版本,以及一个版本因为它不是。最近有一个很好的阐述&#34;检测成语&#34;在Tartan Llama的博客here上以各种C ++标准。这样做的好处是你会得到类似于virtual
给你的东西,有可能选择覆盖函数,但调度决策是在编译时做出的,你不会被迫拥有一个vtable。如果优化器可以向自己证明,例如,所有这些函数都可能被内联。在某些使用它的编译单元中,实际上只有一种类型通过这种机制传递给你的API。
不同之处在于,此InterfacePtr
是非拥有的,并且没有dtor或拥有其指向的对象的存储空间。
如果您希望InterfacePtr
拥有std::function
,并将仿函数复制到自己的记忆中,并在遗漏超出范围时将其删除,那么我建议您使用std::any
代表对象而不是void *
,并在我的实现中使用lambdas中的std::any_cast
而不是static_cast<T*>
。对std::any
进行了一些很好的进一步讨论,以及为什么它对/r/cpp
here上的这个用例有好处。
我不认为有任何办法可以做你原来要求的事情,并恢复原来的&#34;来自std::function
的仿函数类型。类型擦除会删除类型,您无法轻松地将其删除。
编辑:您可能考虑的另一种选择是使用类型擦除库,如dyno
答案 3 :(得分:0)
std::function<X>::target<T>
只能投回完全 T*
。
这是因为存储如何转换为可以转换为T*
的每种类型都需要存储更多信息。在C ++的一般情况下,它需要将信息转换为指向派生的指针。
target
旨在简单地允许用std::function
替换一些函数指针样式的机器并使现有的机器工作,并且几乎为零成本(仅比较类型)。将成本扩展到存储类型的每种基本类型都很难,因此它没有完成,并且不会是免费的,所以它可能在将来不会完成。
std::function
只是类型擦除的一个示例,您可以使用其他功能自行推送。
我要做的第一件事是我会取消你的virtual operator()
。基于类型擦除的多态性并不需要它。
第二件事是开始any
- boost::any
或c++17&#39; s std::any
。
为您编写类型擦除的难点 - 小缓冲区优化和值存储 -
添加您自己的调度表。
template<class Sig>
struct my_extended_function;
template<class R, class...Args>
struct my_extended_function<R(Args...)> {
struct vtable {
R(*f)(any&, Args&&...) = 0;
void*(*cast)(any&, std::type_info const&) = 0;
};
template<class...Bases, class T>
my_extended_function make_with_bases( T t ) {
return {
get_vtable<T, Bases...>(),
std::move(t)
};
}
R operator()(Args...args)const {
return (*p_vtable->f)(state, std::forward<Args>(args)...);
}
private:
template<class T, class...Bases>
static vtable make_vtable() {
vtable ret{
// TODO: R=void needs different version
+[](any& ptr, Args&&...args)->R {
return (*any_cast<T*>(ptr))(std::forward<Args>(args)...);
},
+[](any& ptr, std::type_info const& tid)->void* {
T* pt = any_cast<T*>(ptr);
if (typeid(pt)==tid) return pt;
// TODO: iterate over Bases, see if any match tid
// implicitly cast pt to the Bases* in question, and return it.
}
};
}
template<class T, class...Bases>
vtable const* get_vtable() {
static vtable const r = make_vtable<T,Bases...>();
return &r;
}
vtable const* p_vtable = nullptr;
mutable std::any state;
my_extended_function( vtable const* vt, std::any s ):
p_vtable(vt),
state(std::move(s))
{}
};