【问题标题】:Code size concerns with variadic templates可变参数模板的代码大小问题
【发布时间】:2021-06-13 19:06:14
【问题描述】:

我正在创建一个同时处理许多不同用户定义类型的算法。假设这些类型中的每一个都具有func1func2,这将是MyAlgorithm 的接口。我可以使用可变参数模板来做到这一点:

template <typename... Args>
class MyAlgorithm{
    // interact with Args using func1 and func2
};

我现在可以像 MyAlgorithm&lt;A, B, C, D, E, F, G&gt; 一样实例化 MyAlgorithm。这可行,但是,我觉得对于我的应用程序,这可能会导致MyAlgorithm 的专业化过多。在实践中,MyAlgorithm 可能首先被实例化,例如MyAlgorithm&lt;A, B, B , A, C, D&gt;,然后是MyAlgorithm&lt;A, A, C, D&gt;,然后是MyAlgorithm&lt;C, F D&gt;,以此类推。

不幸的是,模板会针对Args 的每个组合进行实例化。例如,这会在在线设置中产生问题。 MyAlgorithm 可以在批量实时输入数据上运行(MyAlgorithm 的本质是它在批量而不是单个输入上运行)。将解析实时输入数据并调用正确的 MyAlgorithm 特化。在这种情况下,用户需要在编译后的代码中创建所有可能的 MyAlgorithm 特化。

MyAlgorithm<A>;
MyAlgorithm<B>;
MyAlgorithm<C>;
MyAlgorithm<A, A>;
MyAlgorithm<A, B>;
MyAlgorithm<A, C>;
MyAlgorithm<B, A>;
...

我有一个可能会有所帮助的想法,但我并没有完全正确。代替 MyAlgorithm 获取模板参数,它将处理专门用于 A, B, Cs 的模板 Proxy 类。

// no template 
class MyAlgorithm{
    // interact with Proxy using func1proxy and func2proxy
    std::vector<Proxy> Args;
};

template<typename T>
class Proxy{
    // define func1proxy and func2proxy using Arg's func1 and func2
    std::unique_ptr<T> Arg; // only member
};

当然这不起作用,因为每个Proxy 特化都是不同的类,所以MyAlgorithm 仍然需要是模板类(而std::vector&lt;Proxy&gt; Args 不能存在)。

有我想做的设计模式吗?是否有减少类实例化数量的设计模式?

一些注意事项:

MyAlgorithm 对所有模板参数一视同仁。该算法在每个模板参数上递归调用func1func2

class MyAlgorithm{
    template<typname T, typename... Args>
    void CallFunc1s(T first, Args... rest){
        T.func1();
        SomeFunction(rest);
    }
};

【问题讨论】:

  • 对于这个非常常见的问题没有统包解决方案。每种情况都是独一无二的。您需要分析可以将模板的哪一部分完全分解为非模板类​​,以最大程度地减少代码膨胀。再多的“设计模式” Buzzword Bingo 都无法替代对代码逻辑的彻底分析和适当的重构。也许如果你清楚地重申你的问题,可以提供一个更实际的建议:你通过提到一个叫做funcfunc2 的东西开始你的问题,这些东西再也不会被提及。这是什么意思?
  • @SamVarshavchik 重点是MyAlgorithm 只通过调用func1func2 与模板参数交互。它不与模板参数的数据成员交互,除非通过func1func2。它 不需要知道模板参数的类型,而只需要知道如何调用该类型的func1func2。我想象可能有一个使用函数指针的解决方案,我用的不多。特别是,如果Args 之一具有func3,则不会调用func3,否则不会。
  • 我不知道“与模板参数交互”是什么意思。模板参数是类型。函数参数是对象和值以及类型。无论如何,正如我所概述的,减少代码膨胀的方法是将尽可能多的代码和数据分解到非模板代码中。
  • 你能把你的例子更具体一点吗?
  • 您应该向我们展示一个minimal reproducible example,其中存在代码膨胀。很难在摘要中为您提供帮助。

标签: c++ templates design-patterns variadic-templates variadic-functions


【解决方案1】:

类型擦除一个由func1/func2调用的类型T。

struct proxy_ref{
  void* pdata=0;
  void(*pfunc1)(void*)=0;
  void(*pfunc2)(void*)=0;
  template<class T>
  proxy_ref(T&& t):
    pdata((void*)std::addressof(t)),
    pfunc1([](void*pvoid){
      ((T*)pvoid)->func1();
    }),
    pfunc2([](void*pvoid){
      ((T*)pvoid)->func2();
    })
  {}
  void func1(){ pfunc1(pvoid); }
  void func2(){ pfunc2(pvoid); }
};

现在获取proxy_refs 的向量并调用func1func2

您的特定问题可能涉及更复杂的签名,甚至是值类型而不是 ref 类型。

存在类型擦除无法解决的像您这样的问题,但尝试这是第一步。

【讨论】:

  • 这正是我一直在寻找的东西。我猜没有内存安全的方法可以做到这一点?
  • @mana 我不知道你所说的内存安全是什么意思。你的意思是价值语义?是的,你可以做价值语义;就像我做 func1 一样擦除复制/移动/销毁 ctors 和 dtor。
  • 我想我被教导说保证内存安全的最好方法是“永远不要使用原始指针”。让我举个例子。我的代码的用户创建了 2 个对象A a; B b;。他们将这些传递给MyAlgorithmMyAlgorithm(a, b);。我相信这样的事情应该等同于MyAlgorithm(proxy_ref&lt;A&gt;(a), proxy_ref&lt;B&gt;(b))。现在,我想防止abproxy_ref 完成之前被删除,但如果proxy_ref 持有指向它们的最后一个指针,我也希望它们被删除。通常我会使用shared_ptr,但那是一个模板类。
  • @mana 代理引用不是模板。其次,明智的选择不是原始指针或共享 ptr,它们是值语义(常规或半常规)、共享所有权语义或引用语义。在这里,我演示了引用语义(非拥有)。其他两个是非常可行的,但是使用共享所有权和你演示的语法会是有毒的......
  • 价值或非拥有参考通常是最好的。如果您的算法运行并且不存储代理,则非拥有引用语义是安全且便宜的。如果没有,我会实现值语义。可变对象的共享所有权非常难以正确,这是一个陷阱。
猜你喜欢
  • 2011-09-28
  • 2012-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 2016-09-20
相关资源
最近更新 更多