部分成员函数模板特化和数据成员访问
Partial member function template specialisation and data member access
我对模板化成员函数的偏特化有疑问。
背景: 目标是计算大型数据集的描述性统计数据,这些数据集太大而无法一次保存在内存中。因此,我有方差和协方差的累加器 classes,我可以在其中逐个推送数据集(一次一个值或更大的块)。仅计算算术平均值的相当简化的版本是
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;
}
}
};
这种累加器的一个特殊优势 class 是可以将不同数据类型的值推入同一累加器 class。
问题: 这适用于所有整型数据类型。然而,累加器 classes 也应该能够通过首先计算绝对值 |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;
}
}
};
在累加器 class 中,委托结构的模板化方法随后被
调用
template <class InputIt>
void push(InputIt first, InputIt last)
{
push_impl<InputIt, typename std::iterator_traits<InputIt>::value_type>::push(first, last);
}
然而,这种技术存在一个问题,即如何访问累加器的私有成员 class。由于它们不同 classes 无法直接访问,而且 push_impl 的方法需要是静态的并且不能访问累加器的非静态成员。
我可以想到以下四种解决问题的方法,各有优缺点:
- 在每次调用 push 时创建一个 push_impl 的实例,由于额外的(可能)性能下降复制.
- 有一个 push_impl 的实例作为累加器 class 的成员变量,这将阻止我将不同的数据类型推入累加器,因为实例必须完全专业化。
- 使累加器的所有成员classpublic传*this给push_impl::push() 调用。由于封装中断,这是一个特别糟糕的解决方案。
- 根据单值版本实现迭代器版本,即为每个元素调用 push() 方法,(可能)由于额外的函数调用而降低性能.
请注意,上述性能下降本质上是理论上的,由于编译器的巧妙内联,可能根本没有问题,但是实际的 push 方法可能要多得多比上面的例子复杂。
一种解决方案比其他解决方案更可取还是我遗漏了什么?
致以诚挚的问候,非常感谢。
push_impl
可以是一个内部 class 模板(如果你使用 c++11)或者你的累加器 class 的朋友 class 模板(这个似乎是使用友元声明的好案例,因为 push_impl
本质上是累加器 class 实现的组成部分,纯粹出于语言原因而分开)。然后你可以使用你的选项 #3(将 this
传递给 push_impl
的静态方法),但不使累加器成员成为 public.
选项 #4 似乎也不错(因为它避免了代码重复),但正如您提到的那样,需要衡量性能影响。
就我个人而言,我可能会选择您的选项 4,毕竟迭代器版本中实际随类型变化的唯一部分是 "single value version"
中的逻辑
然而,另一种选择是编写您的迭代器版本以通过引用接收均值和大小,然后可以更新均值和大小而无需制作它们 public。
这也有助于测试,因为它允许 push_impl 单独测试(尽管使用这种方法您可能会认为这不再是函数的最佳名称)
顺便说一句,你的 push_impl 最好只在迭代器类型上进行模板化,你可以按照你目前在你的调用示例,但仅将迭代器类型作为参数,就不可能意外地使用错误的值类型调用它(如果值类型可以转换为您传递的类型,这可能并不总是导致编译错误 "T")
如评论所述,您根本不需要为此使用偏特化,实际上偏特化通常很容易避免,而且最好避免。
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
是一个(私有)成员函数,您不需要再做任何特殊的事情。
与您提出的解决方案相比,这没有额外的性能成本。它是相同数量的函数调用,唯一的区别是按值传递无状态类型,这对编译器来说是一个完全微不足道的优化。封装也没有任何牺牲。样板文件略少。
我对模板化成员函数的偏特化有疑问。
背景: 目标是计算大型数据集的描述性统计数据,这些数据集太大而无法一次保存在内存中。因此,我有方差和协方差的累加器 classes,我可以在其中逐个推送数据集(一次一个值或更大的块)。仅计算算术平均值的相当简化的版本是
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;
}
}
};
这种累加器的一个特殊优势 class 是可以将不同数据类型的值推入同一累加器 class。
问题: 这适用于所有整型数据类型。然而,累加器 classes 也应该能够通过首先计算绝对值 |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;
}
}
};
在累加器 class 中,委托结构的模板化方法随后被
调用template <class InputIt>
void push(InputIt first, InputIt last)
{
push_impl<InputIt, typename std::iterator_traits<InputIt>::value_type>::push(first, last);
}
然而,这种技术存在一个问题,即如何访问累加器的私有成员 class。由于它们不同 classes 无法直接访问,而且 push_impl 的方法需要是静态的并且不能访问累加器的非静态成员。
我可以想到以下四种解决问题的方法,各有优缺点:
- 在每次调用 push 时创建一个 push_impl 的实例,由于额外的(可能)性能下降复制.
- 有一个 push_impl 的实例作为累加器 class 的成员变量,这将阻止我将不同的数据类型推入累加器,因为实例必须完全专业化。
- 使累加器的所有成员classpublic传*this给push_impl::push() 调用。由于封装中断,这是一个特别糟糕的解决方案。
- 根据单值版本实现迭代器版本,即为每个元素调用 push() 方法,(可能)由于额外的函数调用而降低性能.
请注意,上述性能下降本质上是理论上的,由于编译器的巧妙内联,可能根本没有问题,但是实际的 push 方法可能要多得多比上面的例子复杂。
一种解决方案比其他解决方案更可取还是我遗漏了什么?
致以诚挚的问候,非常感谢。
push_impl
可以是一个内部 class 模板(如果你使用 c++11)或者你的累加器 class 的朋友 class 模板(这个似乎是使用友元声明的好案例,因为 push_impl
本质上是累加器 class 实现的组成部分,纯粹出于语言原因而分开)。然后你可以使用你的选项 #3(将 this
传递给 push_impl
的静态方法),但不使累加器成员成为 public.
选项 #4 似乎也不错(因为它避免了代码重复),但正如您提到的那样,需要衡量性能影响。
就我个人而言,我可能会选择您的选项 4,毕竟迭代器版本中实际随类型变化的唯一部分是 "single value version"
中的逻辑然而,另一种选择是编写您的迭代器版本以通过引用接收均值和大小,然后可以更新均值和大小而无需制作它们 public。
这也有助于测试,因为它允许 push_impl 单独测试(尽管使用这种方法您可能会认为这不再是函数的最佳名称)
顺便说一句,你的 push_impl 最好只在迭代器类型上进行模板化,你可以按照你目前在你的调用示例,但仅将迭代器类型作为参数,就不可能意外地使用错误的值类型调用它(如果值类型可以转换为您传递的类型,这可能并不总是导致编译错误 "T")
如评论所述,您根本不需要为此使用偏特化,实际上偏特化通常很容易避免,而且最好避免。
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
是一个(私有)成员函数,您不需要再做任何特殊的事情。
与您提出的解决方案相比,这没有额外的性能成本。它是相同数量的函数调用,唯一的区别是按值传递无状态类型,这对编译器来说是一个完全微不足道的优化。封装也没有任何牺牲。样板文件略少。