从std :: function获取基类目标

时间:2018-01-02 15:36:53

标签: c++ c++11 polymorphism std-function

如果我在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

中的强制转换

4 个答案:

答案 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;在Tar​​tan 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&#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))
  {}
};