Указатель на странное объявление члена
Я недавно видел следующий код:
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
? Я признаю, что раньше не видел этот синтаксис.
2 ответа
Это стандартный болтовой указатель на синтаксис члена.
Рассмотрим нормальный указатель:
int *
Вот, *
говорит вам, что это указатель; int
говорит вам тип объекта, на который он указывает. Сейчас в
T1 T2::*
T2::*
говорит вам, что это указатель на член класса T2
, Это так же, как *
в нормальных указателях. На какой тип члена он указывает? T1
, как int
в int *
,
Указатели на члены определяют, как получить доступ к конкретному члену объекта типа, по сравнению с обычными указателями, которые дают абсолютный адрес объекта. Чтобы иметь доступ к члену, вам нужно два элемента: указатель на член и объект, к которому вы его примените.
Декларация 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;
Когда компилятор встречает 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;
});
Это также сделает более очевидным в месте вызова, что вы используете только x
член и может поднять вопрос о том, должны ли вы восстанавливать связи lhs.x == rhs.x
со вторым членом...