c++ - 我该如何创建一个类,以对对象进行类型删除,直到调用了一个函数,而没有事先指定可能的函数列表?

标签 c++ polymorphism c++14 type-erasure

背景

标题听起来似乎很困惑,所以让我解释一下。首先,这是minimal version of my implementation,因此您可以更轻松地遵循这些概念。如果您看过Sean Parent的一些演讲,您会知道他想出了一种抽象多态性的方法,允许这样的代码:

std::vector<Drawable> figures{Circle{}, Square{}};
for (auto &&figure : figures) {draw(figure);}

请注意,没有指针或任何东西。在draw上调用Drawable将在包含的对象上调用适当的draw函数,而对象的类型不易于访问。一个主要的缺点是必须为每个任务编写类似于Drawable的类。我试图对此进行抽象,以便该类不必知道该函数。我当前的解决方案如下:
std::vector<Applicator<Draw>> figures{Circle{}, Square{}};
for (auto &&figure : figures) {figure.apply(Draw{});}

在这里,Draw是带有operator()(Circle)opeator()(Square)的泛函,或者是通用版本。这样,这也是一种访客模式实现。例如,如果您还想打印每个图形的名称,则可以执行Applicator<Draw, PrintName>。调用apply时,将选择所需的功能。

我的实现通过将可调用类型的boost::variant传递给虚函数并使其访问该变量并在其中调用该函数来工作。总的来说,我会说这种实现是可以接受的,但是我还没有考虑允许任何数量的参数或返回类型,更不用说因函数而异的参数了。



我花了几天的时间来尝试一种无需将Applicator用作模板即可进行这项工作的方法。理想情况下,用法将与此类似。为了简单起见,假设调用的函数必须具有签名void(ObjectType)
//For added type strictness, I could make this Applicator<Figure> and have 
//using Figure<struct Circle> = Circle; etc
std::vector<Applicator> figures{Circle{}, Square{}}; 
for (auto &&figure : figures) {figure.apply(Draw{});} //or .apply(draw); if I can

问题通常归结为以下事实:对象类型只能在调用它的函数中获得。在内部,该类使用虚函数,这意味着没有模板。调用apply时,会发生以下情况(与肖恩的讲话相同):
  • 在具有派生类的运行时类型的指向基类的指针上调用内部基类的apply。
  • 调用被调度到派生类,派生类知道存储对象的类型。

  • 因此,当我有了对象时,必须将调用的函数简化为类中已知的单个类型,该类既知道要调用的函数又接受对象。我无法为自己的生活想出办法。

    尝试次数

    这是几次失败的尝试,因此您可以了解为什么我觉得很困难:

    前两个函数的前提是要拥有一个保存函数调用的类型,该函数调用要减去未知的第一个参数(存储的对象)。这至少需要在可调用对象的类型上进行模板化。通过使用Sean Parent的技术,可以很容易地制作一个可以存储在FunctionCall<F>中的GenericFunctionCall类,就像Circle中的Figure一样。可以将此GenericFunctionCall传递到虚函数中,而另一个则不能。

    尝试1
  • apply()用已知的可调用对象类型进行调用。
  • 可调用对象的类型用于创建FunctionCall<Type>并将其存储为类型擦除的GenericFunctionCall
  • GenericFunctionCall对象传递给虚拟apply函数。
  • 派生类获取调用对象,并将该对象用作第一个可用参数。
  • 出于不允许将虚函数用作模板的相同原因,GenericFunctionCall可以在右侧FunctionCall<Type>上调用必要的函数,但不能转发第一个(存储的对象)参数。

  • 尝试2

    作为尝试1的延续:
  • 为了将存储的对象传递到GenericFunctionCall上调用的函数中,可以将存储的对象类型擦除为GenericObject
  • 可能有以下两种情况之一:
  • 调用了一个函数,并为其指定了适当的FunctionCall<Type>,但是给了它一个GenericObject,在其上调用了函数,其类型未知。回想一下,该函数无法在函数调用类型上进行模板化。
  • 会调用一个函数,并为其指定一个表示存储对象的正确T,但具有一个GenericFunctionCall可从中提取正确的函数调用类型。我们回到了派生类的apply函数的起点。

  • 尝试3
  • 在调用apply时采用已知类型的可调用对象,并使用它来制作一些存储可以调用的函数的函数,该函数可以使用已知的存储对象类型(例如std::function)进行调用。
  • 将其键入到boost::any中,然后将其传递给虚拟函数。
  • 当派生类中已知存储的对象类型时,将其强制转换回适当的类型,然后将对象传递给它。
  • 意识到,这整个方法要求在调用apply时知道存储的对象类型。


  • 是否有任何聪明的主意可以将此类转变为不需要模板参数的类,而是可以采用任何可调用对象并与存储对象一起调用它?

    附言我愿意征询比Applicatorapply更好的名称的建议。

    最佳答案

    这是不可能的。考虑一个由三个翻译单元组成的程序:

    // tu1.cpp
    void populate(std::vector<Applicator>& figures) {
      figures.push_back(Circle{});
      figures.push_back(Square{});
    }
    
    // tu2.cpp
    void draw(std::vector<Applicator>& figures) {
      for (auto &&figure : figures) { figure.apply(Draw{}); }
    }
    
    // tu3.cpp
    void combine() {
      std::vector<Applicator>& figures;
      populate(figures);
      draw(figures);
    }
    

    每个TU必须确实可以因果隔离地单独翻译。但是,这意味着没有一个编译器可以同时访问DrawCircle,因此永远不会生成Draw调用Circle::draw的代码。

    关于c++ - 我该如何创建一个类,以对对象进行类型删除,直到调用了一个函数,而没有事先指定可能的函数列表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26303718/

    相关文章:

    c++ - 从C++中的txt文件中读取字符

    c++ - 如何在C++文件中声明标识符JSON?

    c++ - QTableWidget 显示滚动条

    c++ - 获取模板中的函数返回类型

    java - 当对象的所有字段都设置为默认值时,为什么实例常量有值?

    c++ - 这篇关于shared_ptr的use_count()的Standardese是什么意思?

    c++ - 静态(可能是 constexpr)数据成员 l​​ambda

    c++ - 移动赋值运算符的异常说明符如何影响移动构造函数的异常说明符?

    C++ 删除 base 或 dynamic_cast 指针?

    java - 如何使用 JAX-B 从 XML 读取所选类的对象(多态)