我最近看过以下代码:
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
合格?我承认我之前没有见过这种语法。
答案 0 :(得分:3)
这是指向成员语法的bog标准指针。
考虑一个普通的指针:
int *
在这里,*
告诉你它是指针; int
告诉您它指向的对象的类型。现在,在
T1 T2::*
T2::*
告诉您它是指向班级T2
成员的指针。它就像普通指针中的*
一样。它指向的成员类型是什么? T1
,就像int
中的int *
一样。
答案 1 :(得分:1)
指向成员的指针定义如何访问该类型对象上的特定成员,与提供对象绝对地址的常规指针进行比较。为了能够访问该成员,您需要两个元素,指向成员的指针和将应用它的对象。
声明T U::*
表示这是一个机制,用于访问类型为T
的对象内的U
类型成员。这两种类型都是必需的,因为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
的地址和它的基础子对象是相同的,在d2
和d3
中,基数与派生类型不对齐,由d2
vptr
引起d3
的情况,anotherbase
因int base::*ptm = &base::member;
b .*ptm = 5;
d1.*ptm = 10;
d2.*ptm = 15;
d3.*ptm = 20;
而存在{/ 1}}。
b.*ptm
当编译器遇到ptm
时,它会将指向成员b
的指针应用于对象b.member
并生成d1.*ptm
。找不到会员所在的位置,无需算术。当遇到d2.*ptm
时会发生同样的情况,因为基础和完整对象是对齐的。当遇到d3.*ptm
或base::*
时,编译器将首先计算基础子对象的地址(指针算术),然后将指向成员的指针应用于该地址。类型Functor
表示需要进行的转换(偏移或虚拟继承的动态计算)。
在这个可以访问真实对象的简化示例中,任何名副其实的编译器实际上都会直接注入成员的地址,但如果这是在不同的翻译单元中,并通过引用访问,则上述描述将适用。
除此之外,您创建的std::sort(std::begin(v), std::end(v),
[](ABC const& lhs, ABC const & rhs) {
return lhs.x < rhs.x;
});
通常会有不良的性能特征,因为您将指针存储到成员并强制使用它。最好将指向成员的指针转换为模板参数,以便编译器具有更好的优化信息。或者,您可以完全避免使用仿函数,只使用具有良好性能的 lambda ,并且可能更容易理解代码的维护者:
x
这也会使您在电话会议中更明显地表示您只使用lhs.x == rhs.x
成员,并且可能会提出是否应该重新加入 tie {{1与第二个成员...