переопределение ковариантных типов возвращаемых виртуальных функций (оба указателя)

0

Я пытаюсь переопределить функцию базового класса. И производный тип, и указатели возврата базового типа, поэтому он должен быть ковариантным в соответствии с некоторыми сообщениями, которые я читал в 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.

  • 0
    void* не является ковариантным с T* - ошибка ясна?
  • 0
    Если это правда, почему он компилируется и запускается с g ++: ideone.com/TwTBRQ
Показать ещё 1 комментарий
Теги:

3 ответа

1
Лучший ответ

В этом случае gcc ошибочен, и VS правильно отклоняет код. Стандарт рассматривает это в 10.3/7, где он определяет, какие ковариантные средства. Это определение требует, чтобы оба типа возвращаемых значений были указателями или ссылками на классы. Поскольку void не является классом, код, который вы представляете, не показывает ковариацию.

Gcc ошибочно принимает код.

4

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++ выдаёт предупреждение при попытке скомпилировать этот код.

  • 0
    +1 за упоминание, что gcc не является строго неправильным, так как это можно считать расширением.
0

MSVC2013 является правильным.

Когда вы позвоните

  ptr->data();

Компилятор не знает, какой метод использовать, это может быть PSBaseObject::data() или PSObject<data>::data();

Поэтому вам нужно исправить свой дизайн.

  • 0
    Но он знает (g ++ знает?). Функция виртуальная. Это вызовет функцию данных дочернего класса. Если он не существует, он вызывает базовый класс: ideone.com/TwTBRQ Итак, какой компилятор здесь?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню