Что если я скажу вам, что вы можете сделать статический полиморфизм с помощью виртуальных функций?


Статья::Статья

Часто виртуальные функции показывают как пример полиморфизма времени выполнения в C++, в противовес полиморфизму времени компиляции (как шаблон, перегрузка функций, CRTP и т.д.).

Почему? Потому что диспетчеризация происходит во время выполнения, а не во время компиляции!

Вы правильно услышали! В этой статье мы рассмотрим, как и почему.

Обратите внимание, что я не буду объяснять, как виртуальный метод реализуется компилятором, потому что это не относится к делу и даже не определено стандартом (даже если все известные мне компиляторы используют vtable).

Пояснения

Начиная с C++11 существуют функции constexpr. Эти функции могут быть оценены во время компиляции, если аргументы известны во время компиляции, это может быть константа типа 5, переменная constexpr или результат функции constexpr. Например, этот код компилируется :

// A simple function, we just added constexpr in the prototype
constexpr int foo(int n)
{
    // The logic is sooooooo complicated
    return n * 2;
}
// A static assert is like a normal assert,
// but it does with with compile time predicate instead of runtime
// (4 * 2) == 8, everything is good
static_assert(foo(4) == 8);
Войти в полноэкранный режим Выход из полноэкранного режима

Ссылка на проводник компилятора

Предикат — это вызываемая функция, которая возвращает значение, проверяемое как булево.

Но этот код не компилируется:

// Same as above, we added constexpr in the prototype
constexpr int bar(int n)
{
    // The logic is more complicated here, if that's possible
    return n * 3; 
}
// 2 * 3 != 7, we have an error
static_assert(bar(2) == 7); 
// error: static assertion failed
//    | static_assert(bar(2) == 7);
Войти в полноэкранный режим Выйти из полноэкранного режима

Ссылка на проводник компилятора

Функция constexpr должна удовлетворять некоторым требованиям, например, в функции constexpr нельзя выполнять ввод/вывод. Но с каждым новым стандартом требований становится все меньше и меньше. Вот требования, которые интересуют нас сегодня:

она не должна быть виртуальной (до C++20).

Это означает, что теперь мы можем сделать виртуальные функции constexpr, поэтому они могут быть оценены во время компиляции вот так:

// The base class
struct Toto
{
    // The constexpr virtual function we will override
    constexpr virtual int triple(int n) const = 0;
};

// The child class
struct Impl: Toto
{
    // Override the method
    constexpr virtual int triple(int n) const override
    {
        // The unexpected logic
        return 3 * n;
    }
};

// Instanciate the implementation
constexpr auto impl = Impl{};
// We need a reference or a pointer, else the dispatch won't work
constexpr const Toto& impl_ref = impl;

constexpr auto a = impl_ref.triple(3);
static_assert(a == 9); 
Войти в полноэкранный режим Выйти из полноэкранного режима

Ссылка на проводник компилятора
И поскольку все делается во время компиляции, мы можем сказать, что это статический полиморфизм.

Вы можете попробовать этот код в C++17, он не скомпилируется, но в C++20 он работает как шарм!

Усиление с помощью consteval


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

Вот пример, который компилируется :

#include <iostream>

// Put the consteval at the same place you would put the constexpr specifier
consteval int do_stuff(int n)
{
    return n + 3;
}

int main()
{
    // It prints 10
    std::cout << do_stuff(7) << std::endl;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Ссылка на проводник компилятора
И пример, который не компилируется:

#include <iostream>

// Same function
consteval int do_stuff(int n)
{
    return n + 3;
}

int main(int argc, char** argv)
{
    // argc is not know at compile time, this is not good
    std::cout << do_stuff(argc) << std::endl;
}
// main.cpp: In function 'int main(int, char**)':
// main.cpp:12:26: error: 'argc' is not a constant expression
//    12 |     std::cout << do_stuff(argc) << std::endl;
Войти в полноэкранный режим Выход из полноэкранного режима

Ссылка на проводник компилятора

Статья::~Статья

Я показал вам некоторые вещи, которые можно сделать в C++ 20 с помощью виртуальных функций, это может показаться не интуитивно понятным, но это возможно.

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

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

Источники

  • https://en.cppreference.com/w/cpp/language/virtual
  • https://en.cppreference.com/w/cpp/language/constexpr
  • https://en.cppreference.com/w/cpp/language/consteval

Оцените статью
devanswers.ru
Добавить комментарий