Получить производный тип через виртуальную функцию базового класса

Я пытаюсь получить производный тип объекта через виртуальную функцию базового класса. Я написал это, которое не компилируется:

struct base {
  virtual base& get_this() {
    return *this;
  }
};

struct derived : base {
  virtual derived& get_this() override {
    return *this;
  }

  void fn();
};


int main () {
  base* pd = new derived();
  derived& x = pd->get_this(); /*ERROR*/
  x.fn();
  return 0;
}

... выдает мне ошибку: я не могу инициализировать derived& из base. Поскольку get_this является виртуальным, почему pd->get_this() возвращает base& вместо derived&? Заранее спасибо!

РЕДАКТИРОВАТЬ:

Спасибо всем за их полезные ответы и извинения за мой поздний ответ. Я должен был указать в исходном сообщении, что меня также интересует решение моей проблемы, а не просто выяснение того, почему вышеприведенное не компилируется. Моя основная проблема заключается в том, что fn уникален для класса derived и не может быть вызван через базовый класс. Использование приведения, безусловно, решает проблему, но я ненавижу писать код с конструкциями if else только для того, чтобы получить правильный тип (также Скотт Мейерс советует не приводить :)). Ответы, кажется, указывают на то, что броски - это путь, который, по крайней мере, обнадеживает, что я не пренебрегаю более "элегантным" решением моей проблемы. Еще раз спасибо!


person linuxfever    schedule 21.10.2013    source источник
comment
Удалил мой ответ, но я по-прежнему считаю, что OP будет лучше использовать простой dynamic_cast, который будет жизнеспособным решением для описанного варианта использования. Никто больше не упомянул об этом в ответах, и то, как ковариация поддерживается в С++, не решает основную проблему ОП.   -  person πάντα ῥεῖ    schedule 21.10.2013
comment
@ g-makulik Ну, да, вы могли (должны были) просто удалить или изменить спорное предложение — остальное точно и хороший ответ. Теперь вы выплеснули ребенка вместе с водой из ванны.   -  person Konrad Rudolph    schedule 21.10.2013
comment
@KonradRudolph Дал еще один (не придирчивые слова). По крайней мере, вы правы: на тот момент рейтинг отрицательных голосов был меньше, чем прирост репутации. Но я нервничаю из-за отрицательных голосов, и опыт показывает, что они никогда не будут отозваны при правильно отредактированных ответах.   -  person πάντα ῥεῖ    schedule 21.10.2013


Ответы (5)


Поддержка ковариантных типов возврата C++ будет работать только в том случае, если вы уже знаете производный тип. Чтобы преобразовать базовый класс в возможно производный класс, просто используйте dynamic_cast<derived>(base_ref), чтобы определить, соответствует ли base_ref фактический производный тип:

int main () {
    base* pd = new derived();
    derived& x = dynamic_cast<derived&>(*pd); // Will throw an exception if pd 
                                          // isn't a 'derived'
    x.fn();
    return 0;
}

Или альтернативно:

int main () {
    base* pd = new derived();
    derived* x = dynamic_cast<derived*>(pd); // Will return nullptr if pd isn't
                                         // a 'derived'
    if(x) {
        x->fn();
    }
    else {
        // dynamic_cast<derived*> failed ...
    }
    return 0;
}

c++ поддерживает ковариантные возвращаемые типы для производных классы, но, как описывают другие ответы, вы не можете получить его, вызвав здесь базовый класс (pd->get_this()).

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

person πάντα ῥεῖ    schedule 21.10.2013

Статический тип pdbase *. Таким образом, когда компилятор ищет функцию-член get_this(), он находит только base::get_this(). Тип возвращаемого значения base::get_this()base&, который нельзя преобразовать в derived&. Отсюда ошибка.

person Phil Miller    schedule 21.10.2013
comment
Я согласен с тем, что это прекрасно объясняет, почему OP получает ошибку при компиляции, но не показывает реальной альтернативы, чтобы избавиться от ошибки. - person πάντα ῥεῖ; 22.10.2013

Я хотел бы добавить к ответу Новелократа, сославшись на раздел 10.3, параграф 8 документа рабочий проект стандарта C++ (нажмите здесь), в котором объясняется, в каком случае статический тип возвращаемого указателя — Derived*, а не Base*. По сути, если бы вы вызывали get_this() через указатель на производный класс, вы бы получили правильный тип без ошибки компилятора.

Вот цитата из стандарта вместе с примером (тоже из стандарта):

Если возвращаемый тип D::f отличается от возвращаемого типа B::f, тип класса в возвращаемом типе D::f должен быть полным в точке объявления D::f или должен быть классом тип D. Когда переопределяющая функция вызывается как последняя переопределяющая переопределяющая функция, ее результат преобразуется в тип, возвращаемый (статически выбранной) переопределяемой функцией (5.2.2). [Пример:

class B { };
class D : private B { friend class Derived; };
struct Base {
    virtual void vf1();
    virtual void vf2();
    virtual void vf3();
    virtual B* vf4();
    virtual B* vf5();
    void f();
};

struct No_good : public Base {
    D* vf4(); // error: B (base class of D) inaccessible
};

class A;
struct Derived : public Base {
    void vf1(); // virtual and overrides Base::vf1()
    void vf2(int); // not virtual, hides Base::vf2()
    char vf3(); // error: invalid difference in return type only
    D* vf4(); // OK: returns pointer to derived class
    A* vf5(); // error: returns pointer to incomplete class
    void f();
};

void g() {
    Derived d;
    Base* bp = &d; // standard conversion:
    // Derived* to Base*
    bp->vf1(); // calls Derived::vf1()
    bp->vf2(); // calls Base::vf2()
    bp->f(); // calls Base::f() (not virtual)
    B* p = bp->vf4(); // calls Derived::pf() and converts the
    // result to B*
    Derived* dp = &d;
    D* q = dp->vf4(); // calls Derived::pf() and does not
    // convert the result to B*
    dp->vf2(); // ill-formed: argument mismatch
}
person Community    schedule 21.10.2013
comment
'если бы вы вызвали get_this() через указатель на производный класс, вы бы получили правильный тип без ошибок компиляции' Итак, c++ ковариантные возвращаемые типы довольно бесполезны, когда мы говорим о самом типе производного класса, не так ли? - person πάντα ῥεῖ; 22.10.2013
comment
@ g-makulik Они позволяют вам получить правильный тип при вызове метода из производного типа. В противном случае вам пришлось бы бросать. - person ; 22.10.2013
comment
Важнейшим моментом для варианта использования OP является то, что вам нужно знать производный тип, чтобы получить правильный ковариантный возвращаемый тип функции, поэтому это не очень помогает для понижения (что, очевидно, здесь хочет OP) . - person πάντα ῥεῖ; 22.10.2013
comment
@ g-makulik Я никогда не утверждал, что целью моего ответа было предложить альтернативу. Я просто подкрепляю ответ Новелократа соответствующими выдержками из стандарта, которые, я думаю, поучительны. Я не предлагал понижающее приведение с помощью dynamic_cast в первую очередь потому, что в большинстве случаев считаю это подозрительным решением. В зависимости от его потребностей, возможно, лучшим решением было бы просто добавить соответствующую виртуальную функцию, которая правильно выполняет правильный код на основе типа динамического объекта. Я имею в виду, именно поэтому в первую очередь были изобретены виртуальные функции. - person ; 22.10.2013
comment
@ g-makulik Кроме того, единственное, о чем явно спрашивал ОП, это почему он получил ошибку компиляции. - person ; 22.10.2013
comment
Я также не хочу распространять нисходящее литье, используя dynamic_cast. Вот почему я упомянул статический полиморфизм, который, ИМХО, в большинстве случаев будет лучшим выбором дизайна. Но требуется больше усилий, чтобы сделать это правильно. - person πάντα ῥεῖ; 22.10.2013
comment
«Кроме того, единственное, о чем явно просил ОП ...» Да, но вы могли ясно видеть его намерения;) ... - person πάντα ῥεῖ; 22.10.2013
comment
@g-makulik Возможно, но я объявлен явным :) - person ; 22.10.2013

C++ поддерживает ковариантный возвращаемый тип. Это означает, что когда вы вызываете get_this() для объекта derived через указатель base, будет вызываться реализация производного.

Однако это не означает, что вызов base::get_this даст вам derived&. Тип возвращаемого значения base::get_thisbase&. если вы хотите получить объект derived, вам придется вызвать get_this через указатель derived (или преобразовать base& в derived&). Обратите внимание, что именно так работает ковариация возвращаемого типа в Java, C++, D...

base* pbase = new base();
base* pderived = new derived();
derived* pderived2 = new derived();

base& a = pbase->get_this();        // call implementation in base, return base&
base& b = pderived->get_this();     // call implementation in derived, return base&
derived& c = pderived2->get_this(); // call implementation in derived, return derived&
person log0    schedule 21.10.2013
comment
Таким образом, c++ ковариантные возвращаемые типы довольно бесполезны, когда мы говорим о самом типе производного класса, а не они? - person πάντα ῥεῖ; 22.10.2013
comment
@ g-makulik На самом деле я не знаю ни одного языка с ковариантным типом возврата, где бы он не работал так: / когда вы манипулируете ссылкой base, это может быть любой тип позади, так как вы можете знать, что должно быть тип x... ? - person log0; 22.10.2013
comment
Я бы добавил, что «выведение» — это не то же самое, что «угадывание» — вы определяете производные базовых классов, обычно конструктивно расширяя и уточняя каждый из них. Это деривация, но деривация по определению, которое вы считаете «полезным», ближе к обратному. Обычно это цена, которую вы платите за абстракцию. - person John P; 26.12.2017

Я нашел простое решение, но если есть возможность, я бы мастера оценить:

class base{ 
    type = 1;
    virtual int getType() final {
        return type;
    }
}

class derived1 : public base {
    derived1(){
        type = 2;
    }
}

Таким образом, вы можете вызвать метод 'int getType()' любого из производных классов. Поскольку тип задается в конструкторе, нет риска неправильного поведения. Чтобы повысить удобство использования, я создал предопределенные «типы».

Я использую, но не знаю, МакГайвери!

person Auradrummer    schedule 17.08.2015
comment
Все эти методы являются закрытыми, а в type отсутствует спецификатор типа. Вы даже не могли создать экземпляр derived1 в своем примере. - person RamblingMad; 17.08.2015
comment
Также решение выше может стать вашей головной болью :) - person Alexander; 22.02.2017
comment
Похоже, что это «утиная типизация», основанная на «магических числах», или ручной/исчерпывающий «перечисленный тип» за вычетом уникальности и централизованного индекса. Я ничего не умею, если не предсказывать головные боли; Я должен поддержать @Alexander в этом вопросе. - person John P; 26.12.2017