指向成员奇怪声明的指针
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
及其基础子对象是相同的,在 d2
和 d3
中,基础与派生类型不对齐, 在 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.*ptm
或 d3.*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
和第二个成员…
我最近看到了以下代码:
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
及其基础子对象是相同的,在 d2
和 d3
中,基础与派生类型不对齐, 在 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.*ptm
或 d3.*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
和第二个成员…