Crazy С++ template - шаблон для доступа к отдельным атрибутам класса

Я начинающий программист на С++, но я думал, что знаю достаточно о С++ до сегодняшнего дня, когда натолкнулся на такой код на работе и не понял, как это работает.

class Object
{
};

template <
        class PropObject,
        class PropType, 
        PropType PropObject::* Prop
        >
class PropReader
{
public:
    void print(Object& o)
    {
        PropObject& po = static_cast<PropObject &>(o);
        PropType& t = po.*Prop;

        cout << t << "\n";
    }
};

class Student : public Object
{
public:
    int age;
    int grade;
};

int _tmain(int argc, _TCHAR* argv[])
{   
    Student s;
    s.age = 10;
    s.grade = 5;

    PropReader<Student, int, &Student::age> r;
    PropReader<Student, int, &Student::grade> r2;

    r.print(s);
    r2.print(s);
}

Я думаю, что я понял на высоком уровне. Но этот конкретный PropType PropObject::* Prop в объявлении шаблона беспокоит меня. Что это значит? Я ищу объяснение от экспертов С++. Я хотел бы понять это, чтобы я мог использовать его лучше. Это выглядит очень полезно, хотя.

Ответы

Ответ 1

Шаблоны С++ хорошо известны для принятия типов в качестве аргументов, но их также можно параметризовать по сравнению с другими типами данных. Например, вы можете templatize класс над целым числом, как показано здесь:

template <typename T, unsigned int N> class Array {
private:
    T array[N];

public:
    /* ... */
};

Шаблоны также можно параметризовать по указателям, если указатель соответствует определенным критериям (например, он должен оценивать адрес того, что может быть определено во время компиляции). Например, это совершенно легально:

template <int* Pointer> class ThisIsLegal {
public:
    void doSomething() {
        *Pointer = 137;
    }
};

В вашем коде шаблон параметризуется над элементом-указателем. Элемент pointer-to-class похож на указатель на то, что он косвенно относится к некоторому объекту. Однако вместо того, чтобы указывать на объект, вместо этого он указывает на поле в классе. Идея состоит в том, что вы можете разыменовать указатель-класс-член относительно некоторого объекта, чтобы выбрать это поле вне класса. Вот простой пример указателей на класс:

struct MyStruct {
    int x, y;
};

int main() {
    MyStruct ms;
    ms.x = 137;
    ms.y = 42;

    int MyStruct::* ptr; // Declare a pointer to a class member.
    ptr = &MyStruct::x;  // Now points to the field 'x'

    ms.*ptr = 0;         // Set the 'x' field of ms to be zero.
}

Обратите внимание, что синтаксис объявления элемента-указателя-класса-класса

Type ContainingClass::* pointerName;

Итак, в приведенном выше коде int MyStruct::* ptr означает "указатель на int внутри класса MyStruct.

В коде, который вы опубликовали, объявление шаблона читается следующим образом:

template <
    class PropObject,
    class PropType, 
    PropType PropObject::* Prop
    >
class PropReader

Посмотрим, что это значит. Первые два объекта аргумента шаблона, чье свойство будет считано, и PropType, тип этого свойства. "Последний аргумент шаблона - это указатель на класс с именем Prop, который указывает внутри a PropObject в поле типа PropType. Например, вы можете создать экземпляр этого шаблона с помощью MyStruct следующим образом:

PropReader<MyStruct, int, &MyStruct::x> myPropReader;

Теперь посмотрим, что делает остальная часть кода. Тело этого шаблона класса перепечатано здесь:

void print(Object& o)
{
    PropObject& po = static_cast<PropObject &>(o);
    PropType& t = po.*Prop;

    cout << t << "\n";
}

Некоторые из них можно легко прочитать. Параметр этой функции является ссылкой на Object с именем o, а последняя строка выводит какое-либо поле. Эти две строки сложны:

PropObject& po = static_cast<PropObject &>(o);
PropType& t = po.*Prop;

Эта первая строка - это приведение типов, которое говорит: "Попробуйте применить аргумент o к типу PropObject. Идея, я предполагаю, заключается в том, что Object - это некоторый базовый класс многих разные объекты. Параметр функции - это просто обычный Object, и этот прилив пытается преобразовать его в что-то из соответствующего типа (напомним, что PropObject - это аргумент шаблона, указывающий, что такое тип объекта). это использует static_cast, если преобразование не определено (например, вы пытались создать экземпляр шаблона над int или vector<string>), код не будет компилироваться. В противном случае код надеется, что литье будет безопасным, затем получает ссылку типа PropObject на значение параметра.

Наконец, последняя строка

PropType& t = po.*Prop;

Это использует синтаксис разыменования элемента-указателя-класса-члена, о котором я упоминал ранее, чтобы сказать "выберите поле, на которое указывает Prop (аргумент шаблона), затем сохраните ссылку на него с именем t.

Итак, короче, шаблон

  • запрашивает тип какого-либо объекта.
  • запрашивает тип какого-либо поля в этом объекте.
  • Предлагает вам указатель на поле в этом объекте.
  • Предоставляет функцию print, которая при попытке объекта распечатать это поле.

Уф! Это было сложно! Надеюсь, это поможет!

Ответ 2

Объявления на C или С++ часто читаются справа налево:

PropType PropObject::* Prop
                       ~~~~  The Prop template parameter
                   ~~~       is a pointer to a member
         ~~~~~~~~~~          of a PropObject
~~~~~~~~                     where that member has type PropType

Это можно увидеть в действии в экземплярах:

PropReader<Student, int, &Student::age> r;

Здесь третий параметр шаблона является указателем на член класса Student, который имеет тип int.

Ответ 3

PropObject::* является указателем на член (в этом случае используется элемент данных). Это в основном указатель на (нестатический) элемент (которые Student::age и Student::grade в случае вашего кода).

Чтобы использовать его, вы должны указать объект this, который будет использовать функция. Это делается с помощью оператора .* или ->*; в этом случае строка PropType& t = po.*Prop; обрабатывает, где po используется как объект this для членов.

Ответ 4

Это синтаксис для передачи указателя на член в качестве аргумента. В частности, в этом случае он может быть передан членам age и grade. Хотя аргументы шаблона указывают класс Student, он является членом и что его свойства int.

Если вы добавили имя класса ученику, объявление PropReader может выглядеть так:

PropReader<Student, std::string, &Student::name> r3;

Ответ 5

Но этот конкретный PropType PropObject:: * Prop в шаблоне выражение беспокоит меня. Что это значит?

PropType PropObject::* Prop

Это определяет указатель, указывающий на переменную-член класса, в данном конкретном случае класс является PropObject, а тип переменной - PropType.