Указатель на странное объявление члена

Я недавно видел следующий код:

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 со вторым членом...

Другие вопросы по тегам