С++: правильное переопределение виртуальной функции в классе шаблона

Рассмотрим следующий код:

template<typename T>
struct MyTempl
{
    virtual void doStuff(const T &value) = 0;
};

struct MyImpl : MyTempl<int>
{
    void doStuff(const int &value) {}
};

struct MyPtrImpl : MyTempl<int*>
{
    void doStuff(const int* &value) {}
};

MyImpl imp1;
MyPtrImpl imp2;

Это не скомпилируется правильно: компилятор говорит мне, что doStuff() в MyPtrImpl является чисто виртуальным, т.е. Я не смог правильно его переопределить. Если я, однако, typedef int*, что-то вроде int_ptr, и использую его как аргумент шаблона для MyPtrImpl, все будет работать.

Почему компилятор не может вычесть мои намерения без typedef?

Ответы

Ответ 1

Вы не указали правильный тип параметра виртуальной функции и, таким образом, вы не переопределяете его. Это приводит к абстрактному классу, поскольку виртуальная функция чиста.

При указании параметра константного ссылочного типа, как

void doStuff(const int & value) {}

это на самом деле альтернатива написанию

void doStuff(int const & value) {}

который следует считать "нормальным" способом объявления постоянной ссылки. При чтении справа налево это означает "ссылка на константу int". Применение одного и того же шаблона к типу указателя int* приводит к

void doStuff(int* const & value) {}

который компилируется отлично. Но это невозможно записать в порядке, как описано выше, поскольку это будет означать что-то другое: прочитайте const int* & справа налево, и вы получите "ссылку на указатель на константу int".

Как вы упомянули, альтернативу, которая работает с порядком сверху, может быть записана, если вы используете typedef для псевдонима типа указателя int*:

typedef int *T;
void doStuff(const T & value) {}

который также компилируется отлично, так как T теперь рассматривается как один тип литерала, не вводящий двусмысленности сверху. Другими словами, единственный способ читать const T & - это "ссылка на const T, которая сама является указателем на int". Это немного сложно понять интуитивно, но невозможно понять это как "ссылку на указатель на константу int", так как в описании не было бы одного T.

Я рекомендую использовать ключевое слово override, если вы используете компилятор С++ 11 для обнаружения таких проблем (есть сценарии, в которых эта проблема не будет приводить к ошибке компиляции, а в непредвиденном поведении; например, если виртуальная функция не была чистой):

void doStuff(int* const & value) override {}
//                               ^^^^^^^^