Принудительное позднее разрешение метода в случае наследования классов в c ++

0

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

    class foo {
    public:
      int fun () {
        cout << "in foo" << endl;
      }
    };

    class bar_class1:public foo {
    public:
      int fun () {
        cout << "in bar_class1" << endl;
      }
    };

    class bar_class2:public foo {
    public:
      float fun () {
        cout << "in bar_class2" << endl;
      }
    };

    main () {
      foo * foo_pointer = new bar_class1();
      foo_pointer->fun();
    }

Вывод вышеуказанной программы находится в foo. Есть ли способ, что с помощью указателя типа foo * который фактически указывает на объект типа bar_class1 или bar_class2, мы можем назвать fun функцию производного класса вместо базового класса? Я не могу сделать функцию fun виртуальной в базовом классе foo так как существует конфликт типа возвращаемого значения для функции foo в производном классе bar_class2.

  • 1
    Можно ли было бы сделать его виртуальным, а затем сделать так, чтобы bar_class2 реализовал int fun() и делегировал его в приватный float fun() ?
  • 1
    Ваш пример немного сбивает с толку, так как функции-члены имеют тип возвращаемого значения, но они ничего не возвращают, и возвращаемое значение не используется вызывающей стороной.
Показать ещё 9 комментариев
Теги:
oop
inheritance
overload-resolution

4 ответа

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

Вот мои комментарии как ответ.

Вы не можете сделать это.

Если бы такой вид полиморфизма был возможен, разве это не нарушило бы ужас, когда код вызывает foo::fun (ожидающий int) на объекте, фактический тип которого является bar_class2 и, таким образом, получает float? Вы хотите просто выбросить безопасность типа?

Если вам нужны разные типы возврата, кажется, что вы хотите создать шаблон. Но вы не можете использовать шаблоны совершенно так, как вы хотите использовать foo(). Статический полиморфизм (шаблоны) и полиморфизм времени выполнения (позднее связывание) плохо смешиваются. Вам нужно перепроектировать структуру вашего oop.

Если вы абсолютно ненавидите безопасность типов, вы можете сделать это с помощью указателей void. Но для любви к Flying Spaghetti Monster никогда не делайте этого в c++. Пожалуйста, закройте глаза, прежде чем читать следующий код, чтобы избежать воздействия.

#include <iostream>

class foo {
public:
    virtual void* fun() = 0;
    virtual ~foo(){};
};

class bar_class1: public foo {
public:
    void* fun() {
        return &value;
    }
private:
    int value = 1;
};

class bar_class2: public foo {
public:
    void* fun() {
        return &value;
    }
private:
    float value = 1.1;
};

int main() {
    foo* foo_pointer1 = new bar_class1();
    foo* foo_pointer2 = new bar_class2();

    // in c++ compiler must know the type of all objects during compilation
    std::cout << *reinterpret_cast<int*>(foo_pointer1->fun()) << '\n';
    std::cout << *reinterpret_cast<float*>(foo_pointer2->fun()) << '\n';
    delete foo_pointer1;
    delete foo_pointer2;
}
  • 0
    Это предложение касти действительно кому-нибудь поможет?
  • 1
    @Wolf Это говорит спрашивающему, что то, что он просит, не может быть сделано безопасным способом. То, что он спрашивает, не имеет никакого смысла в безопасном языке. Я полагаю, что мой ответ помогает всем, кто рассматривает этот тип дизайна, понять, что им нужно изменить дизайн (надеюсь, код поможет им понять, почему они должны). Как сделать этот редизайн, во многом зависит от того, что они собираются делать со своими классами. Невозможно сказать, что ОП собирается сделать по его вопросу.
3

Возможно, похоже на существующий ответ, я действительно надеюсь, что вы понимаете, что изменить дизайн лучше, чем этот беспорядок, но я считаю, что это лучшее, что вы получите. Я заставляю вас указывать тип возврата на callsite (например, someFoo->fun<int>()), так как вам все равно придется это знать, и отправьте его на основе этого. Любой забавный бизнес, и вы получите исключение. Также имейте в виду, что производительность этого, я думаю, менее желательна.

#include <cassert>
#include <stdexcept> 
#include <type_traits> 

struct foo {       
    virtual ~foo() = default;

    template<typename T, typename = typename std::enable_if<std::is_same<T, int>::value>::type, typename = void>
    T fun();

    template<typename T, typename = typename std::enable_if<std::is_same<T, float>::value>::type>
    T fun();
};

struct bar_class1 : foo {
    int fun() {
        return 2;
    }
};

struct bar_class2 : foo {
    float fun() {
        return 3.5f;
    }
};

template<typename T, typename, typename Dummy>
T foo::fun() {
    if (auto *p = dynamic_cast<bar_class1 *>(this)) {
        return p->fun();
    } else if (dynamic_cast<bar_class2 *>(this)) {
        throw std::invalid_argument("Mismatching dynamic type.");
    } else {
        return 1;
    }
}

template<typename T, typename>
T foo::fun() {
    auto *p = dynamic_cast<bar_class2 *>(this);

    if (dynamic_cast<bar_class1 *>(this) || !p) {
        throw std::invalid_argument("Mismatching dynamic type.");
    } else if (auto *p = dynamic_cast<bar_class2 *>(this)) {
        return p->fun();
    }

    assert(false); //should never get here, but compiler doesn't know that
}

Если вам нужна основная функция, я написал полный образец.

1

Чтобы ответить на ваш вопрос: Нет, поздняя привязка невозможна без определения типа возврата.... по крайней мере, не в разумном порядке, см. user2079303 отличный контрпример. Но...

вы можете изменить свой код (например) на что-то вроде следующего, используя ключевое слово virtual и сравните тип возврата, например, с void:

class foo 
{
public:
    virtual void fun(std::ostream& out) {
        out << "in foo" << std::endl;
    }
};

поэтому вы можете выбрать тип вывода позже:

class intFoo: public foo 
{
public:
    void fun(std::ostream& out)  {
        // output an int
        out << "in bar_class1. data: " << data << endl;
    }
    int data;
};

class floatFoo: public foo 
{
public:
    void fun(std::ostream& out)  {
        // output a float 
        out << "in bar_class2. data: " << data << endl;
    }
    float data;
};

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

  • 1
    Это приведет к ошибке из-за bar_class2::fun , но с несовместимым типом возврата.
  • 0
    Извините, я упустил эту деталь, теперь она исправлена.
Показать ещё 4 комментария
0

Функция fun не является виртуальной функцией, так как вы не использовали ключевое слово "virtual" для ее украшения. Таким образом, компилятор определит, какую функцию вызывать при компиляции. Таким образом, нет возможности сообщить компилятору вызывать другую функцию, потому что компилятор будет использовать свой статический тип, то есть тип определения переменной - foo *.

Ещё вопросы

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