7

C++ 模板通常被同化为膨胀的创造者,而 Shim 的想法正是针对这一点:使模板只是常规函数的薄包装。这是减少臃肿的好方法。

例如,让我们使用一个简单的垫片:

//
// Shim interface
//
struct Interface {
    virtual void print(std::ostream& out) const = 0;
}; // struct Interface

std::ostream& operator<<(std::ostream& out, Interface const& i) {
    i.print(out);
    return out;
}

template <typename T>
struct IT: public Interface {
    IT(T const& t): _t(t) {}
    virtual void print(std::ostream& out) const { out << _t; }
    T const& _t;
};

template <typename T>
IT<T> shim(T const& t) { return IT<T>(t); }

现在,我可以像这样使用它:

void print_impl(Interface const& t);

template <typename T>
void print(T const& t) { print_impl(shim(t)); }

而且无论如何print_impl实现,print仍然非常轻量级,应该内联。十分简单。


然而,C++11 引入了可变参数模板。典型的冲动是使用 C++11 可变参数模板重新实现所有不安全的 C 可变参数,甚至 Wikipedia 也建议使用printfimplementation

不幸的是,维基百科的实现不处理位置参数:允许您指定打印第三个参数的类型,等等......如果我们有一个带有这个原型的函数,那就很容易了:

void printf_impl(char const* format, Interface const* array, size_t size);

或类似的。

现在,我们如何从原始界面桥接

template <typename... T>
void printf(char const* format, T const&... t);

到上面的签名?

shims 的一个困难是它们依赖于对 const-ref 行为的绑定来延长临时包装器的生命周期,而不必动态分配内存(如果这样做的话,它们不会便宜)。

一步完成绑定+数组转换似乎很困难。特别是因为语言中不允许引用数组(和指向引用的指针)。


对于那些感兴趣的人,我有一个解决方案的开始:

//
// printf (or it could be!)
//
void printf_impl(char const*, Interface const** array, size_t size) {
    for (size_t i = 0; i != size; ++i) { std::cout << *(array[i]); }
    std::cout << "\n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    Interface const* array[sizeof...(t)] = { (&t)... };
    printf_impl(format, array, sizeof...(t));
}

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_bridge(format, ((Interface const&)shim(t))...);
}

但是您会注意到引入了一个补充步骤,这有点烦人。尽管如此,它似乎仍然有效。

如果有人提出更好的实施方案,我将不胜感激。


@Potatoswatter 建议使用初始化列表,这有点帮助(没有范围)。

void printf_impl(char const*, std::initializer_list<Interface const*> array) {
    for (Interface const* e: list) { std::cout << *e; }
    std::cout << "\n";
}

template <typename... T>
void printf_bridge(char const* format, T const&... t) {
    printf_impl(format, {(&t)...});
}

但仍然没有解决中间函数问题。

4

2 回答 2

3

使其轻量级取决于消除类型参数化。您的 shim 可能会使用表达式实例化一些重任务out << _t,因此它可能不是一个很好的例子。

C varargs 通过将所有内容隐式转换为intptr_t. 如果您只想复制 Cprintf功能,您可以使用reinterpret_cast和执行相同的操作initializer_list

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_impl(format, { reinterpret_cast< std::intptr_t >( t ) ... } );
}

这显然不是最理想的,但垫片本质上是有限的。如果你愿意,你可以用多态类型做其他事情initializer_list

无论如何,这正是initializer_list它的目的。它只能从一个花括号初始化列表构造,使其大小成为编译时常量。但是大小只能作为运行时常量读回。因此,它唯一的实际用途是将仅在列表长度上不同的模板汇集到常见的可变长度实现中。

再加上initializer_list参数的生命周期语义——对象在堆栈上的一个连续数组中创建,并在函数调用语句结束时死亡——initializer_list看起来很像<varargs>!(编辑:或您的解决方案,我现在实际上已经回去阅读:vP)

编辑:由于容器不能直接存储多态对象,并且智能指针不适合临时参数对象,因此实现多态需要将指针指向临时对象。丑陋,但由于临时对象的生命周期保证是合法的:

template <typename... T>
void printf(char const* format, T const&... t) {
    printf_impl(format, std::initializer_list< Interface const * >
        { & static_cast< Interface const & >( shim(t) )... } );
}
于 2012-04-15T11:13:10.997 回答
0

如果您可以使用同质(内存中的大小和对齐方式相同)类型,请查看

// thin template layer over regular class/methods
template< typename T, typename... Contracts>
inline void Container::bindSingleAs(){

isMultiBase< T, Contracts...>(); //compile time test

    priv::TypeInfoP         types[ sizeof...( Contracts)]
                            { &typeid( Contracts)... };

    priv::SharedUpcastSignature upcasts[ sizeof...( Contracts)]
                            { &priv::shared_upcast< T, Contracts>... };

    // dispatch over non-template method.
    container->bindSingleAs( &typeid(T), types, upcasts, sizeof...(   Contracts));
}

现在由于评论而编辑后,我认为有两个相互矛盾的要求。

  1. 想要一个数组参数
  2. 不想要复制开销

如果printf_impl函数需要一个数组作为参数,那么这意味着数组元素应该在内存中具有相同的配置(这意味着如果 1 个元素是 64 字节,那么即使它们是 1 个字节,也会强制所有其他元素对齐 64 字节。 .)因此需要一个副本,或者至少需要一个指向固定位置的指针的副本,所以绝对不可能做 OP 想要的。

我们仍然可以构建该数组,但我们受到限制

  1. 我们根本不想复制,那么我们应该静态声明数组的类型,这迫使我们构建第三种类型。

    auto Array = MakeArray( /* values*/);

    printf( Array);

  2. 我们接受复制,所以我们在函数内部构建数组,因为值未知,我们可以对用户隐藏数组,但我们必须将参数复制到固定的内存位置,但是我们仍然将数组隐藏在引擎盖下。

  3. 堆分配,允许在一个非常紧凑的数组中传递参数,但是参数必须驻留在其他地方并且堆分配可能很昂贵。

第一个解决方案是通过创建一个静态类型的数组来接受额外的编码复杂性,该数组可以处理元素(全部与最大元素对齐),但是这是次优的,因为增加对象的大小可能会影响性能(如果即使在函数调用之后数组仍然存在)

第二种解决方案隐藏了模板接口背后的复杂性,但是它无法避免将值临时复制到与第一种解决方案相同的数组的性能成本。

所以,这是不可能的,对不起。另一个答案介于 2 和 3 之间。所有其他可能的答案都在 3 个类别之一内。

于 2016-05-18T12:44:39.200 回答