Рубрики

Любопытно повторяющийся шаблон (CRTP)

Фон:

Рекомендуется ссылаться на виртуальные функции и полиморфизм времени выполнения . Ниже приведен пример программы для демонстрации полиморфизма во время выполнения.

// Простая программа на C ++ для демонстрации времени выполнения
// полиморфизм
#include <iostream>
#include <chrono>

using namespace std;

  

typedef std::chrono::high_resolution_clock Clock;

  
// Для сохранения размеров изображения

class Dimension

{

public:

    Dimension(int _X, int _Y) {mX = _X;  mY = _Y; }

private:

    int mX, mY;

};

  
// Базовый класс для всех типов изображений

class Image

{

public:

    virtual void Draw() = 0;

    virtual Dimension GetDimensionInPixels() = 0;

protected:

    int dimensionX;

    int dimensionY;

};

  
// Для изображений Tiff

class TiffImage : public Image

{

public:

    void Draw() { }

    Dimension GetDimensionInPixels() {

        return Dimension(dimensionX, dimensionY);

    }

};

  
// Может быть больше производных классов, таких как PngImage,
// BitmapImage и т. Д.

  
// Код драйвера, который вызывает виртуальную функцию

int main()

{

    // Тип изображения

    Image* pImage = new TiffImage;

  

    // Сохраняем время до вызова виртуальной функции

    auto then = Clock::now();

  

    // вызываем Draw 1000 раз, чтобы убедиться в производительности

    // виден

    for (int i = 0; i < 1000; ++i)

        pImage->Draw();

  

    // Сохраняем время после вызова виртуальной функции

    auto now = Clock::now();

  

    cout << "Time taken: "

         << std::chrono::duration_cast

           <std::chrono::nanoseconds>(now - then).count()

         << " nanoseconds" << endl;

  

    return 0;

}

Выход :

Time taken: 2613 nanoseconds

Смотрите это для приведенного выше результата.

Когда метод объявляется виртуальным, компилятор тайно делает для нас две вещи:

  1. Определяет VPtr в первых 4 байтах объекта класса
  2. Вставляет код в конструктор для инициализации VPtr для указания на VTable

Что такое VTable и VPtr?
Когда метод объявляется виртуальным в классе, компилятор создает виртуальную таблицу (она же VTable) и сохраняет адреса виртуальных методов в этой таблице. Затем создается виртуальный указатель (он же VPtr) и инициализируется для указания на этот VTable. VTable совместно используется всеми экземплярами класса, то есть компилятор создает только один экземпляр VTable для совместного использования всеми объектами класса. Каждый экземпляр класса имеет свою собственную версию VPtr. Если мы распечатаем размер объекта класса, содержащего хотя бы один виртуальный метод, вывод будет sizeof (данные класса) + sizeof (VPtr).
Поскольку адрес виртуального метода хранится в VTable, VPtr можно манипулировать, чтобы вызывать эти виртуальные методы, что нарушает принципы инкапсуляции. Смотрите ниже пример:

// Программа на C ++, демонстрирующая, что мы можем напрямую
// манипулировать VPtr. Обратите внимание, что эта программа основана
// при условии, что компилятор хранит vPtr в
// конкретный способ достижения полиморфизма во время выполнения.
#include <iostream>

using namespace std;

  
#pragma pack(1)

  
// Базовый класс с виртуальной функцией foo ()

class CBase

{

public:

    virtual void foo() noexcept {

        cout << "CBase::Foo() called" << endl;

    }

protected:

    int mData;

};

  
// Производный класс с собственной реализацией
// из foo ()

class CDerived : public CBase

{

public:

    void foo() noexcept  {

        cout << "CDerived::Foo() called" << endl;

    }

private:

    char cChar;

};

  
// Код драйвера

int main()

{

    // Указатель базового типа, указывающий на производный

    CBase *pBase = new CDerived;

  

    // Доступ к vPtr

    int* pVPtr = *(int**)pBase;

  

    // Вызов виртуального метода

    ((void(*)())pVPtr[0])();

  

    // Изменение vPtr

    delete pBase;

    pBase = new CBase;

    pVPtr = *(int**)pBase;

  

    // Вызывает метод для нового базового объекта

    ((void(*)())pVPtr[0])();

  

    return 0;

}

Выход :

CDerived::Foo() called
CBase::Foo() called 

Мы можем получить доступ к vPtr и через него делать вызовы виртуальным методам. Представление объектов в памяти объясняется здесь .

Разумно ли использовать виртуальный метод?
Как видно, через указатель базового класса отправляется вызов метода производного класса. Кажется, все работает нормально. Тогда в чем проблема?
Если виртуальная подпрограмма вызывается много раз (порядка сотен тысяч), она снижает производительность системы. Причина заключается в том, что каждый раз, когда вызывается подпрограмма, ее адрес необходимо разрешать, просматривая VTable с использованием VPtr. Дополнительная косвенность (разыменование указателя) для каждого вызова виртуального метода делает доступ к VTable дорогостоящей операцией, и лучше избегать его настолько, насколько мы можем.

Любопытно повторяющийся шаблон (CRTP)

Использование VPtr и VTable можно полностью избежать с помощью CURLY CURURURING Template Pattern (CRTP). CRTP — это шаблон проектирования в C ++, в котором класс X является производным от экземпляра шаблона класса, используя сам X в качестве аргумента шаблона. В более общем смысле это известно как F-связанный полиморфизм.

// Программа изображения (аналогичная вышеописанной) для демонстрации
// работа CRTP
#include <iostream>
#include <chrono>

using namespace std;

  

typedef std::chrono::high_resolution_clock Clock;

  
// Для сохранения размеров изображения

class Dimension

{

public:

    Dimension(int _X, int _Y)

    {

        mX = _X;

        mY = _Y;

    }

private:

    int mX, mY;

};

  
// Базовый класс для всех типов изображений. Шаблон
// параметр T используется для определения типа производного
// класс, на который указывает указатель.

template <class T>

class Image

{

public:

    void Draw()

    {

        // Отправление вызова на точный тип

        static_cast<T*> (this)->Draw();

    }

    Dimension GetDimensionInPixels()

    {

        // Отправление вызова на точный тип

        static_cast<T*> (this)->GetDimensionInPixels();

    }

  

protected:

    int dimensionX, dimensionY;

};

  

  
// Для изображений Tiff

class TiffImage : public Image<TiffImage>

{

public:

    void Draw()

    {

        // Раскомментируем это для проверки отправки метода

        // cout << "TiffImage :: Draw () named" << endl;

    }

    Dimension GetDimensionInPixels()

    {

        return Dimension(dimensionX, dimensionY);

    }

};

  
// Может быть больше производных классов, таких как PngImage,
// BitmapImage и т. Д.

  
// Код драйвера

int main()

{

    // Указатель типа изображения, указывающий на Tiffimage

    Image<TiffImage>* pImage = new TiffImage;

  

    // Сохраняем время до вызова виртуальной функции

    auto then = Clock::now();

  

    // вызываем Draw 1000 раз, чтобы убедиться в производительности

    // виден

    for (int i = 0; i < 1000; ++i)

        pImage->Draw();

  

    // Сохраняем время после вызова виртуальной функции

    auto now = Clock::now();

  

    cout << "Time taken: "

         << std::chrono::duration_cast

         <std::chrono::nanoseconds>(now - then).count()

         << " nanoseconds" << endl;

  

    return 0;

}

Выход :

Time taken: 732 nanoseconds

Смотрите это для приведенного выше результата.

Виртуальный метод против эталонного CRTP
Время, затраченное на использование виртуального метода, составило 2613 наносекунд. Этот (небольшой) выигрыш в производительности от CRTP объясняется тем, что использование диспетчеризации VTable было обойдено. Обратите внимание, что производительность зависит от многих факторов, таких как используемый компилятор, операции, выполняемые виртуальными методами. Числа производительности могут отличаться в разных прогонах, но от CRTP ожидается (небольшой) выигрыш в производительности.
Примечание. Если мы напечатаем размер класса в CRTP, можно увидеть, что VPtr больше не резервирует 4 байта памяти.

 кут)

Вопросов? Держите их в пути. Мы хотели бы ответить.

Ссылки)
https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Эта статья предоставлена Aashish Barnwal . Если вам нравится GeeksforGeeks и вы хотите внести свой вклад, вы также можете написать статью и отправить ее по почте на contrib@geeksforgeeks.org. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.

Пожалуйста, напишите комментарии, если вы обнаружите что-то неправильное, или вы хотите поделиться дополнительной информацией по обсуждаемой теме

Рекомендуемые посты:

Любопытно повторяющийся шаблон (CRTP)

0.00 (0%) 0 votes