Я пытаюсь переопределить функцию базового класса. И производный тип, и указатели возврата базового типа, поэтому он должен быть ковариантным в соответствии с некоторыми сообщениями, которые я читал в google и здесь, в stackoverflow.
Однако в MSVC2013 для следующих классов:
class PSBaseObject
{
public:
PSBaseObject() {}
virtual ~PSBaseObject() {}
virtual void* data() { return this; }
virtual const void* data() const { return this; }
};
template<typename T>
class PSObject : public PSBaseObject
{
private:
T* data_;
public:
PSObject(T* object) : data_(object) {}
~PSObject() { delete data_; }
T* data() { return data_; }
const T* data() const { return data_; }
};
Я получаю сообщение об ошибке:
'PSObject<data>::data': overriding virtual function return type differs and is not covariant from 'PSBaseObject::data'
Если данные определены и используются следующим образом:
typedef struct
{
void* hFileMap;
void* pData;
std::size_t size;
} data;
data* info = new data();
auto ptr = new PSObject<data>(info);
Почему это не ковариантно?
Любые идеи, что я делаю неправильно в MSVC2013
? Код компилируется и отлично работает в g++ 4.8.1
.
В этом случае gcc ошибочен, и VS правильно отклоняет код. Стандарт рассматривает это в 10.3/7, где он определяет, какие ковариантные средства. Это определение требует, чтобы оба типа возвращаемых значений были указателями или ссылками на классы. Поскольку void
не является классом, код, который вы представляете, не показывает ковариацию.
Gcc ошибочно принимает код.
MSVC прав; void*
не ковариантно с T*
. Цитирование из стандарта (10.3 [class.virtual], раздел 7):
Возвращаемый тип переопределяющей функции должен быть либо идентичен возвращаемому типу переопределенной функции, либо ковариант с классами функций. Если функция
D::f
переопределяет функциюB::f
, возвращаемые типы функций ковариантны, если они удовлетворяют следующим критериям:- оба являются указателями на классы, оба являются значениями lvalue для классов или оба являются значениями rvalue для классов
- класс в возвращаемом типе
B::f
является тем же классом, что и класс возвращаемого типаD::f
, или является однозначным и доступным прямым или косвенным базовым классом класса в возвращаемом типеD::f
- оба указателя или ссылки имеют одинаковую cv-квалификацию, а тип класса в возвращаемом типе
D::f
имеет ту же самую cv-квалификацию, что или менее cv-qualification, чем тип класса в возвращаемом типеB::f
.
void
не является базовым классом T
(= data
), поэтому возвращаемые типы не являются ковариантными.
Так почему это правило? Ну, идея в том, что если у вас есть
struct B {
virtual U* f();
};
struct D : B {
virtual V* f();
};
B* b1 = new B();
B* b2 = new D();
U* u1 = b1->f();
U* u2 = b2->f();
b1->f()
будет вызывать B::f
, что возвращает U*
. Но b2->f()
будет вызывать D::f
, который возвращает V*
. V
должен быть получен из U
так что V*
возвращаемый из D::f
всегда может быть преобразован в U*
.
Теперь разумно разрешить U
быть void
в этом случае, потому что любой указатель на тип объекта может быть преобразован в void*
, хотя void
не является базовым классом. Но стандарт не требует, чтобы это было разрешено.
В стандарте также говорится (1.4 [intro.compliance], пункт 8),
Соответствующая реализация может иметь расширения (включая дополнительные функции библиотеки) при условии, что они не изменят поведение какой-либо хорошо сформированной программы. Реализации необходимы для диагностики программ, которые используют такие расширения, которые плохо сформированы в соответствии с этим Международным стандартом. Однако, сделав это, они могут компилировать и выполнять такие программы.
Таким образом, g++ не ошибается. Разрешение U
быть void
является расширением, которое не изменяет поведение какой-либо хорошо сформированной программы, а g++ выдаёт предупреждение при попытке скомпилировать этот код.
MSVC2013 является правильным.
Когда вы позвоните
ptr->data();
Компилятор не знает, какой метод использовать, это может быть PSBaseObject::data()
или PSObject<data>::data();
Поэтому вам нужно исправить свой дизайн.
void*
не является ковариантным сT*
- ошибка ясна?