指向成员奇怪声明的指针

pointer to member strange declaration

我最近看到了以下代码:

template <typename T1, typename T2> 
class Functor
{
    Functor( T1 T2::* t): memPointer(t) {}
    bool operator() ( const T2 &obj1, const T2 &obj )
    {

        return obj1.*memPointer < (obj.*memPointer);

    }

    T1 T2::* memPointer;
};

这里 Functor 用作通用仿函数,用于根据数据成员对对象进行排序,即它的用法类似于

struct ABC
{
    double x;
    double y;
};

int main()
{
    std::vector<ABC> v; 
    // initialize v with ABCs
    Functor<double, ABC> fun(&ABC::x);
    std::sort(std::begin(v), std::end(v), fun); // sort with respect to `ABC::x`
}

我不得不说我不明白 Functor 是如何工作的。更具体地说,Functor::Functor 构造函数的类型是什么? T2::* 应该是指向成员的指针,但为什么它用 T1 限定?我承认我以前没见过这种语法。

这是指向成员语法的沼泽标准指针。

考虑一个普通指针:

int * 

这里,*告诉你这是一个指针; int 告诉你它指向的对象的类型。现在,在

T1 T2::*

T2::* 告诉您它是指向 class T2 成员的指针。就像普通指针中的 * 一样。它指向的成员的类型是什么? T1,就像 int * 中的 int

与给出对象绝对地址的常规指针相比,指向成员的指针定义了如何访问该类型对象上的特定成员。为了能够访问成员,您需要两个元素,指向成员的指针和您将在其上应用它的对象。

声明T U::*意味着这是一个机制来访问类型U对象中类型T的成员。这两种类型都是必需的,因为 T 决定了将要访问的内容,并且需要 U 来了解 如何 访问它。特别是,在存在继承的情况下,您可以使用指向成员的指针和派生类型的对象作为基类,无论基类和派生类型是否对齐,编译器都会做正确的事情:

struct base { int member; }                          b;
struct derived1 : base {}                            d1;
struct derived2 : base { virtual void ~derived2(); } d2;
struct anotherbase { int y; };
struct derived3 : anotherbase, base {}               d3;

在上面的代码中,完整对象的地址 d1 及其基础子对象是相同的,在 d2d3 中,基础与派生类型不对齐, 在 d2 的情况下由于 vptr, 在 d3 的情况下由于存在 anotherbase.

int base::*ptm = &base::member;
b .*ptm = 5;
d1.*ptm = 10;
d2.*ptm = 15;
d3.*ptm = 20;

当编译器遇到 b.*ptm 时,它将指向成员 ptm 的指针应用到对象 b 并产生 b.member。无需算术即可找到成员的住所。当它遇到 d1.*ptm 时也会发生同样的情况,因为基础对象和完整对象是对齐的。当它遇到 d2.*ptmd3.*ptm 时,编译器将首先计算基本子对象的地址(指针算法),然后将指向成员的指针应用于该地址。类型 base::* 表示需要进行何种转换(偏移量,或虚拟继承情况下的动态计算)。 在这个可以访问真实对象的简化示例中,任何名副其实的编译器实际上都会直接注入成员的地址,但如果这是在不同的翻译单元中,并通过引用访问,则上述描述将适用。

除此之外,您创建的 Functor 通常具有较差的性能特征,因为您正在存储指向成员的指针并强制使用它。您最好将指向成员的指针转换为模板参数,以便编译器有更好的信息来优化。或者,您可以完全避免仿函数,而只使用 lambda,它具有良好的性能,并且对于代码的维护者来说可能更容易理解:

std::sort(std::begin(v), std::end(v), 
          [](ABC const& lhs, ABC const & rhs) {
               return lhs.x < rhs.x;
          });

这也会让您在通话地点更加明显地知道您只使用了 x 成员,并且可能会提出您是否应该解除 ties[=52] 的问题=] lhs.x == rhs.x 和第二个成员…