【发布时间】:2017-09-07 15:40:03
【问题描述】:
我有一个关于模板化成员函数的部分专业化的问题。
背景:目标是计算大型数据集的描述性统计数据,这些数据集太大而无法一次保存在内存中。因此,我有方差和协方差的累加器类,我可以在其中逐个推入数据集(一次一个值或更大的块)。仅计算算术平均值的相当简化的版本是
class Mean
{
private:
std::size_t _size;
double _mean;
public:
Mean() : _size(0), _mean(0)
{
}
double mean() const
{
return _mean;
}
template <class T> void push(const T value)
{
_mean += (value - _mean) / ++_size;
}
template <class InputIt> void push(InputIt first, InputIt last)
{
for (; first != last; ++first)
{
_mean += (*first - _mean) / ++_size;
}
}
};
这种累加器类的一个特别优点是可以将不同数据类型的值推送到同一个累加器类中。
问题:这适用于所有整数数据类型。然而,累加器类也应该能够通过首先计算绝对值 |z| 来处理复数。然后将其推入蓄能器。对于推送单个值,很容易提供重载方法
template <class T> void push(const std::complex<T> z)
{
T a = std::real(z);
T b = std::imag(z);
push(std::sqrt(a * a + b * b));
}
对于通过迭代器推送数据块,但情况并不那么简单。为了正确地重载,需要部分特化,因为我们需要知道实际的(完全特化的)复数类型。通常的方法是将实际代码委托到内部结构中并相应地对其进行专门化
// default version for all integral types
template <class InputIt, class T>
struct push_impl
{
static void push(InputIt first, InputIt last)
{
for (; first != last; ++first)
{
_mean += (*first - _mean) / ++_size;
}
}
};
// specialised version for complex numbers of any type
template <class InputIt, class T>
struct push_impl<InputIt, std::complex<T>>
{
static void push(InputIt first, InputIt last)
{
for (; first != last; ++first)
{
T a = std::real(*first);
T b = std::imag(*first);
_mean += (std::sqrt(a * a + b * b) - _mean) / ++_size;
}
}
};
在累加器类中,委托结构的模板化方法随后被调用
template <class InputIt>
void push(InputIt first, InputIt last)
{
push_impl<InputIt, typename std::iterator_traits<InputIt>::value_type>::push(first, last);
}
但是,这种技术存在一个问题,即如何访问累加器类的私有成员。由于它们是不同的类,因此无法直接访问,而且 push_impl 的方法需要是静态的,不能访问累加器的非静态成员。
我可以想到以下四种解决问题的方法,它们各有优缺点:
- 在每次调用 push 时创建一个 push_impl 实例,但由于额外的副本(可能)会降低性能。
- 有一个 push_impl 的实例作为累加器类的成员变量,这将阻止我将不同的数据类型推送到累加器中,因为该实例必须完全专门化。
- 公开累加器类的所有成员并将 *this 传递给 push_impl::push() 调用。由于封装中断,这是一个特别糟糕的解决方案。
- 根据单值版本实现迭代器版本,即为每个元素调用 push() 方法,由于额外的函数调用,性能(可能)下降。
请注意,所提到的性能下降本质上是理论上的,由于编译器的巧妙内联,可能根本没有问题,但是实际的 push 方法可能比示例中的复杂得多以上。
一种解决方案比其他解决方案更可取还是我错过了什么?
最好的问候和非常感谢。
【问题讨论】:
-
函数模板的部分特化也可以通过标签调度技术来模拟。
-
我喜欢选项 1。它干净、封装良好,您应该会发现,在任何合理的编译器上,“创建”和“复制”一个无状态类作为局部变量实际上不会编译为任何内容。通常最好从干净的代码开始,然后再进行优化。
标签: c++ templates template-specialization partial-specialization