代码之家  ›  专栏  ›  技术社区  ›  Crashworks

C++中结构图映射函数的最清晰编码方法

  •  3
  • Crashworks  · 技术社区  · 15 年前

    这是一个关于最可读的方法的观点的民意测验——是否使用C++指针到成员,字节偏移,或模板化的函子来定义“从结构FO中选择成员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...
    }
    

    我的函数只执行 cubic interpolation 在可用样本之间的某个给定时间点内猜测这些条件。

    // 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 );
    

    这是最直截了当和最简单的方式,但它看起来像是一堆额外的类型、语法和临时类定义。它编译的东西与上面两个几乎完全相同,但它也在可执行文件中转储了一堆冗余的函数定义。(我已经用 /FAcs 但也许链接器会再次将它们取出。)


    以上三个都可以工作,编译器为它们发出几乎相同的代码;因此,我必须做的最重要的选择是 哪个可读性最好 .你怎么认为?

    4 回复  |  直到 15 年前
        1
  •  1
  •   Phil Miller    15 年前

    如果你的团队由相当聪明的人组成,我会说要相信他们和他们的能力,并使用指向成员语法提供的技术上首选的解决方案。这就是它的用途。

    如果你真的很担心,你可以采取一些措施来缓解未来的麻烦。

    • 在typedef附近的注释中注意到这种用法称为“指向成员的指针”语法,以便其他团队成员知道要查找什么
    • 在代码审查中明确指出,其中许多都应该存在。如果它被认为不可理解或太模糊而无法维护,请主动更换。

    其他两种方法都存在问题,正如您所描述的,甚至更严重:

    • 两者都需要更多的代码、更多的打字空间等。
    • 这个 offsetof Primitive的应用类型有限:

      由于C++中结构的扩展功能,在这种语言中,OffStof的使用被限制为“POD类型”,对于类,或多或少对应于结构的C概念(尽管非公共类仅具有公共非虚成员函数,并且没有构造函数和/或析构函数也可以称为POD)。

    here .

        2
  •  3
  •   Khaled Alshaya    15 年前

    在这种情况下,我发现模板化的函数非常清晰。

    ReadingAtTime<WindSelector>( 12.6f, data );
    
        3
  •  2
  •   UncleBens    15 年前

    更简单的方法是使用一个通用函数,它通过指向成员的指针使访问看起来像一个函数调用。它可能看起来像这样:

    #include <functional>
    
    template <class T, class Result>
    class member_pointer_t: public std::unary_function<T, Result>
    {
        Result T::*member;
    public:
        member_pointer_t(Result T::*m): member(m) {}
        Result operator()(const T& o) const { return o.*member; }
    };
    
    template <class T, class Result>
    member_pointer_t<T, Result> member_pointer(Result T::*member)
    {
        return member_pointer_t<T, Result>(member);
    }
    
    float ReadingAtTime( float time, const std::vector<TricorderReadings> &data, member_pointer_t<TricorderReadings, float> f )
    {
       int idx = FindClosestSampleBefore( time, data );
       return CubicInterp( time, data[idx-1].time, f(data[idx-1]));
    }
    
    ReadingAtTime( 12.6f, data, &TricorderReadings::windspeed);
    

    该示例还包括一个帮助函数,用于帮助推导该函数的模板参数(本示例中未使用)。

    函数readingattime还可以接受模板化函数:

    template <class Func>
    float ReadingAtTime( float time, const std::vector<TricorderReadings>& data, Func f);
    
    ReadingAtTime( 12.6f, data, member_pointer(&TricorderReadings::windspeed));
    

    这样,您就可以使用各种函数/函数从数据[IDX-1]中获取值,而不仅仅是指向成员的指针。

    成员指针的更通用的等价物可能是std::tr1::bind或std::tr1::mem_fn。

        4
  •  1
  •   MadCoder    15 年前

    对于简单的事情,我更喜欢指向成员解决方案的指针。但是,函数方法有两个可能的优点:

    1. 将算法与 数据允许您使用算法 为了将来的更多事情,sine 它能与你提供的任何东西一起工作 无法构造正确的函数。

    2. 与1相关,这可能使 像你一样,更容易测试算法 有方法向 不涉及的功能 创建完整的数据对象 打算使用。你可以用更简单的 模拟对象。

    但是,我认为只有当你所做的函数非常复杂和/或在许多不同的地方使用时,函数方法才是值得的。