Кызыктай кайталануучу шаблон үлгүсү (CRTP) жана статикалык полиморфизм
Виртуалдык функцияларга кошумча чыгымсыз альтернатива катары компиляция учурундагы диспетчеризация
class Widget : public Base<Widget>. Base так туунду типти компиляция учурунда билгендиктен, ал туунду класстын методдорун static_cast аркылуу түз чакыра алат, виртуалдык таблицасыз, кыйыр чакыруусуз жана толук ичине киргизүү (inlining) менен. Ушул түрдө полиморфизм компиляция учурундагы чечимге айланат.
- виртуалдык диспетчеризациянын аткаруу учурундагы баасын жана аны качан четтеткен жакшы экенин түшүндүрө аласыз,
- CRTP түзүлүшүн окуп-жаза аласыз жана бул жерде
static_castэмне үчүн коопсуз экенин түшүнөсүз, - CRTP'ди статикалык интерфейстер үчүн, жалпы жүрүм-турумду примес (mixin) катары киргизүү үчүн жана оператордук шаблон кодду түзүү үчүн колдоносуз,
- компиляция учурундагы диспетчеризацияны аткаруу учурундагы диспетчеризацияга каршы адилет салмактап, туура куралды тандай аласыз.
1. Кайталоо: аткаруу учурундагы полиморфизм канчага турат
Сиз виртуалдык функцияларды башталгыч жана орто курстардан билесиз. Методду virtual деп белгилеп, аны базалык көрсөткүч же шилтеме аркылуу чакырганда, компилятор конкреттүү типти биле албайт, ошондуктан диспетчеризация аткаруу учурунда виртуалдык функциялар таблицасы (vtable) аркылуу болот: ар бир полиморфтук объект өз классынын функция даректеринин таблицасына көрсөткөн жашыруун vptr алып жүрөт.
Бул ийкемдүүлүк акысыз эмес. Ар бир виртуалдык чакыруу - бул компилятор адатта алдын ала биле албаган көрсөткүч аркылуу болгон кыйыр чакыруу, демек:
- максат компиляция учурунда белгисиз, ошондуктан чакырууну ичине киргизүүгө болбойт жана оптимизатор аны аркылуу көрө албайт;
- ар бир объект бир көрсөткүчкө (
vptr) чоңоёт, бул майда жана көп сандагы объекттер үчүн маанилүү; - кыйырдык ысык циклдерде тармактын туура эмес божомолдонушуна же кэш өткөрүп жиберүүсүнө себеп болушу мүмкүн.
struct Shape {
virtual double area() const = 0; // vtable аркылуу аткаруу учурунда чечилет
virtual ~Shape() = default;
};
double totalArea(const std::vector<Shape*>& v) {
double s = 0;
for (const Shape* p : v)
s += p->area(); // кыйыр чакыруу, ичине киргизүүгө болбойт
return s;
}
Коддун көбү үчүн бул баа анча маанилүү эмес, ал эми ийкемдүүлүк дал сизге керектүү нерсе. Бирок конкреттүү тип компиляция учурунда мурунтан белгилүү болсо - сандык ядро, туюнтма шаблону, контейнер адаптери - динамикалык диспетчеризацияга төлөө таза текке кетүү. CRTP «туунду класстын ишке ашырылышын чакыруу» формасын сактап, аны компиляция учурунда чечүүгө мүмкүндүк берет.