这是一个关于最易读的方式做某事的民意调查——是使用指向成员的 C++ 指针、字节偏移量还是模板化仿函数来定义“从结构 foo 中选择成员 X”。
我有一个包含大量结构向量的类型,并且我正在编写一个实用函数,该函数基本上在其中一些范围内作为reduce操作。每个结构都将一组因变量与独立维度上的某个点相关联——为了发明一个简化的例子,想象这记录了一个房间随着时间的推移的一系列环境条件:
// all examples are psuedocode for brevity
struct TricorderReadings
{
float time; // independent variable
float tempurature;
float lightlevel;
float windspeed;
// etc for about twenty other kinds of data...
}
我的函数只是执行三次插值来猜测可用样本之间某个给定时间点的这些条件。
// performs Hermite interpolation between the four samples closest to given time
float TempuratureAtTime( float time, sorted_vector<TricorderReadings> &data)
{
// assume all the proper bounds checking, etc. is in place
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time,
data[idx-1].time, data[idx-1].tempurature,
data[idx+0].time, data[idx+0].tempurature,
data[idx+1].time, data[idx+1].tempurature,
data[idx+2].time, data[idx+2].tempurature );
}
我想概括这个函数,以便它可以普遍应用于任何成员,而不仅仅是温度。我可以想到三种方法来做到这一点,虽然它们都可以直接编码,但我不确定一年后必须使用它的人最易读的方法是什么。这是我正在考虑的:
指向成员的语法
typedef int TricorderReadings::* selector;
float ReadingAtTime( time, svec<TricorderReadings> &data, selector whichmember )
{
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time, data[idx-1].time, data[idx-1].*whichmember,
/* ...etc */ );
}
// called like:
ReadingAtTime( 12.6f, data, &TricorderReadings::windspeed );
这感觉像是最“C++y”的方式,但它看起来很奇怪,而且整个指向成员的语法很少使用,因此我团队中的大多数人都很难理解。这是技术上“正确”的方式,但也是我会收到最困惑的电子邮件的方式。
结构偏移
float ReadingAtTime( time, svec<TricorderReadings> &data, int memberoffset )
{
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time,
data[idx-1].time,
*(float *) ( ((char *)(&data[idx-1]))+memberoffset ),
/* ...etc */ );
}
// called like:
ReadingAtTime( 12.6f, data, offsetof(TricorderReadings, windspeed) );
这在功能上与上述相同,但显式地执行指针数学运算。这种方法对于我团队中的每个人(他们都在 C++ 之前学习 C)都会立即熟悉和理解,而且它很健壮,但看起来很恶心。
模板化函子
template <class F>
float ReadingAtTime( time, svec<TricorderReadings> &data )
{
int idx = FindClosestSampleBefore( time, data );
return CubicInterp( time,
data[idx-1].time,
F::Get(data[idx-1]) ),
/* ...etc */ );
}
// called with:
class WindSelector
{
inline static float Get(const TricorderReadings &d) { return d.windspeed; }
}
ReadingAtTime<WindSelector>( 12.6f, data );
这是最直接和 STL 式的做事方式,但它看起来像是一大堆额外的类型和语法以及临时的类定义。它编译成与上面两个几乎完全相同的东西,但它还在整个可执行文件中转储了一堆冗余函数定义。(我已经用/FAcs验证了这一点,但也许链接器会再次将它们取出。)
以上三个都可以工作,编译器为它们发出几乎相同的代码;所以,我必须做出的最重要的选择就是最易读的。你怎么看?