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 也建议使用printf
implementation。
不幸的是,维基百科的实现不处理位置参数:允许您指定打印第三个参数的类型,等等......如果我们有一个带有这个原型的函数,那就很容易了:
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)...});
}
但仍然没有解决中间函数问题。