Шаблон Crazy C++ — шаблон для доступа к отдельным атрибутам класса.

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

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 в объявлении шаблона меня беспокоит. Что это значит? Я ищу объяснение от экспертов C++. Я хотел бы понять это, чтобы я мог использовать его лучше. Хотя выглядит очень полезно.


person cgcoder    schedule 22.07.2011    source источник


Ответы (5)


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

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

public:
    /* ... */
};

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

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

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

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, который указывает внутри 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.

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

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

Вау! Это было сложно! Надеюсь это поможет!

person templatetypedef    schedule 22.07.2011
comment
Это был потрясающий ответ templatetypedef .. неудивительно, что у вас такое имя пользователя :) - person cgcoder; 22.07.2011

Объявления в C или 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.

person sth    schedule 22.07.2011

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

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

person Chris Jester-Young    schedule 22.07.2011

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

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

PropReader<Student, std::string, &Student::name> r3;
person AJG85    schedule 22.07.2011

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

PropType PropObject::* Prop

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

person Gob00st    schedule 22.07.2011