部分成员函数模板特化和数据成员访问

时间:2017-09-07 15:40:03

标签: c++ templates template-specialization partial-specialization

我有一个关于模板化成员函数的部分特化的问题。

背景:目标是计算大型数据集的描述性统计信息,这些数据集太大而无法一次保留在内存中。因此,我有方差和协方差的累加器类,我可以逐个推入数据集(一次一个值或更大的块)。仅计算算术平均值的相当简化的版本是

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;
        }
    }
};

在accumulator类中,委托结构的模板化方法随后由

调用
template <class InputIt>
void push(InputIt first, InputIt last)
{
    push_impl<InputIt, typename std::iterator_traits<InputIt>::value_type>::push(first, last);
}

然而,这种技术存在一个问题,即如何访问累加器类的私有成员。由于它们是不同的类,因此无法直接访问,而且push_impl的方法必须是静态的,不能访问累加器的非静态成员。

我可以想到以下四个解决问题的方法,它们都有各自的优点和缺点:

  1. 在每次调用 push 时创建一个 push_impl 的实例,由于额外的副本,可能会降低性能。
  2. push_impl 的实例作为累加器类的成员变量,这将阻止我将不同的数据类型推送到累加器中,因为实例必须完全专用。
  3. 使累加器类的所有成员都公开,并将 * this 传递给 push_impl :: push()调用。由于封装中断,这是一个特别糟糕的解决方案。
  4. 根据单值版本实现迭代器版本,即为每个元素调用 push()方法,由于额外的函数调用,可能会降低性能。
  5. 请注意,所提到的降低性能本质上是理论上的,并且可能完全没有问题,因为编译器可以进行巧妙的内联,但是实际的 push 方法可能比来自上方。

    一种解决方案优于其他解决方案还是我错过了什么?

    致以最诚挚的问候和非常感谢。

3 个答案:

答案 0 :(得分:1)

如评论所述,您根本不需要使用部分特化,实际上部分特化通常很容易避免,并且首选避免使用。

private:
template <class T>
struct tag{}; // trivial nested struct

template <class I, class T> 
void push_impl(I first, I last, tag<T>) { ... } // generic implementation

template <class I, class T>
void push_impl(I first, I last, tag<std::complex<T>>) { ... } // complex implementation

public:
template <class InputIt>
void push(InputIt first, InputIt last)
{
    push_impl(first, last,
              tag<typename std::iterator_traits<InputIt>::value_type> {});
}

由于push_impl是(私人)会员功能,因此您不需要再做任何特别的事情了。

与您提出的解决方案相比,这没有额外的性能成本。它的函数调用次数相同,唯一的区别是按值传递无状态类型,这对编译器来说是一个完全无关紧要的优化。并且封装也没有牺牲。并且稍微少了样板。

答案 1 :(得分:0)

Activity可以是内部类模板(如果你使用c ++ 11)或者你的累加器类的朋友类模板(这似乎是使用友元声明的好例子,因为{{1}本质上是累加器类实现的一个组成部分,纯粹出于语言原因而分离。然后,您可以使用选项#3(将push_impl传递给push_impl的静态方法),但不要将累加器成员公开。

选项#4似乎也不太糟糕(因为它避免了代码重复),但正如您所提到的那样,需要测量性能影响。

答案 2 :(得分:0)

我个人可能会选择你的选项4,毕竟迭代器版本中实际上因类型而异的部分是&#34;单值版本中的逻辑&#34;

然而,另一种选择是编写迭代器版本以通过引用接收均值和大小,然后可以更新均值和大小,而不必将它们公开。

这也有助于测试,因为它允许单独测试push_impl(尽管使用这种方法,你可能会认为它不再是函数的最佳名称)

顺便说一句,你的push_impl最好只在迭代器类型上进行模板化,你可以用你当前在调用示例中的方式推导出push_impl中的值类型,但只有迭代器类型为一个参数,没有机会用错误的值类型意外调用它(如果值类型可以转换为您传递的类型,则可能不会总是导致编译错误&#34; T&#34;)< / p>