Как я могу определить виртуальную функцию, которая принимает производный тип в качестве параметра?

У меня есть родительский класс Obj с пустой виртуальной функцией cmp

class Obj{
 public:
   virtual int cmp(const Obj& val) = 0;
   ...
};

Я пытаюсь определить эту функцию в подклассе MyString, но вместо const Obj& в качестве аргумента я использую const MyString&, что, вероятно, вызывает ошибку. Виртуальная функция Emprty Obj::cmp не имеет повторного определения

class MyString : public Obj{
private:
    ...
public:
    virtual int cmp(const MyString& val){
        ... using private values of MyString
    }
};

Итак, как я могу это решить, если у меня есть 3-4 подкласса, которые используют свои собственные переменные в этой функции


person RomaFUN    schedule 09.12.2020    source источник
comment
Вы хотите перегрузить, а не переопределить. Пожалуйста, проверьте это, чтобы получить ответ: stackoverflow.com/ questions / 30638634 /   -  person Luan Pham    schedule 09.12.2020
comment
@LuanPham прав. Если вы хотите изменить аргументы, это называется перегрузкой. Сообщение об ошибке, которое вы получаете, связано с тем, что вы не определяете чисто виртуальную функцию своего базового класса в MyString.   -  person FloWil    schedule 09.12.2020
comment
Это известно как проблема двоичного метода, найдите ее. В объектно-ориентированной парадигме нет удовлетворительного решения, работающего со статической типизацией. Основная ошибка заключается в использовании Obj (или, по крайней мере, cmp метода в Obj) в первую очередь.   -  person n. 1.8e9-where's-my-share m.    schedule 09.12.2020
comment
C ++ не поддерживает контравариантность, и в любом случае должно быть наоборот Base::foo(Derived&) и Derived::foo(Base&).   -  person Jarod42    schedule 09.12.2020


Ответы (2)


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

class Obj
{
    public:
        virtual int cmp(const Obj& val) = 0;
        ...
};

class MyString : public Obj
{
    private:
        ...
    public:
        int cmp(const Obj& val)
        {
            // cast val to MyString& (you can use dynamic_cast)
            ... using private values of MyString
        }
};

Также вы можете использовать перегрузку. В этом случае вам не нужен виртуальный метод в базовом классе.

class Obj
{
    public:
        int cmp(const Obj& val)
        {
             // implementation for Obj
        }
        ...
};

class MyString : public Obj
{
    private:
        ...
    public:
        int cmp(const MyString& val)
        {
            ... using private values of MyString
        }
};
person Gor Asatryan    schedule 09.12.2020
comment
Это все неправильно. Актерский состав противоречит цели строгой типизации. Терминология обратная: первый пример является переопределяющим, а второй - перегрузочным. Вы также, вероятно, захотите другую подпись во втором. - person n. 1.8e9-where's-my-share m.; 09.12.2020
comment
Я просто использовал dynamic_cast, как упоминалось в первом примере, и у меня это сработало. - person RomaFUN; 09.12.2020
comment
@RomaFUN Конечно, это работает, когда вы передаете правильный тип cmp. Однако цель статической типизации - вызвать ошибку времени компиляции, если вы попытаетесь передать неверный тип. Наличие dynamic_cast означает, что больше не будет ошибки времени компиляции, а будет ошибка времени выполнения. Это ваше решение, если вас это устраивает. - person n. 1.8e9-where's-my-share m.; 09.12.2020
comment
@ n.'pronouns'm. Извини за ошибку. уже исправлено - person Gor Asatryan; 09.12.2020
comment
@ n.'pronouns'm. Если настроить шаблон абстрактного базового класса / производного класса, чтобы разрешить полиморфизм, то, безусловно, одним из основных преимуществ является то, что коду не нужно знать конкретный тип MyString во время компиляции, только то, что он реализует Obj. Так что я мог подумать, что проверка ошибок во время выполнения идет с территорией? - person DS_London; 09.12.2020
comment
@DS_London Нет, обычно вам не нужно вставлять проверки типов во время выполнения и быстрые ошибки типов во время выполнения в каждый метод, когда речь идет о наследовании. Это обещание статической типизации. Важно иметь возможность доверять сигнатурам ваших функций. cmp (const Obj&) означает, что любые Obj здесь допустимы. Классы, производные от Obj, нарушают это обещание. Если вы не можете полагаться на рекламируемые типы, зачем вообще нужны статические типы? - person n. 1.8e9-where's-my-share m.; 09.12.2020
comment
@ n.'pronouns'm. Извините, я не понял. Если вам нужен полиморфный cmp (const Obj & val), чтобы вы могли хранить динамический список Obj (например) и сравнивать экземпляры во время выполнения. Проверка статического типа работает в том смысле, что она настаивает на том, чтобы параметр производного класса имел реализацию cmp () с правильной подписью: это не гарантирует, что параметр будет делать то, что вы хотите, когда вы вызываете cmp (). Возможно, я слишком много читаю между строк о намерениях спрашивающего. - person DS_London; 09.12.2020
comment
@DS_London Если вам нужен полиморфный cmp (const Obj & val), я хочу сказать, что желание этой вещи бесполезно. это не гарантирует, что параметр будет делать то, что вы хотите. Я не хочу никаких ошибок типа времени выполнения. Вот почему у меня в первую очередь статические типы. Если статические типы не могут мне этого гарантировать, мне лучше без них. Обычно они встают у меня на пути (оправданно), когда я пытаюсь написать программу с неверным типом. Таким образом, они ограничивают меня, но дают взамен преимущество: отсутствие ошибок типа. Если я все равно смогу написать программу с неверным типом, они просто ограничат меня, ничего не дав взамен. - person n. 1.8e9-where's-my-share m.; 09.12.2020

Мне приходит в голову пример любопытно повторяющегося шаблона шаблона. Это выглядит так:

#include <iostream>
#include <ostream>

template <typename T>
struct Base
{
    virtual int cmp(T const &) = 0;
};

struct First : Base<First>
{
    virtual int cmp(First const &);
};

int First::cmp(First const &)
{
    std::cout << "First\n";
    return 1;
}

struct Second : Base<Second>
{
    virtual int cmp(Second const &);
};

int Second::cmp(Second const &)
{
    std::cout << "Second\n";
    return 2;
}

int main()
{
    Base<First>* f1 = new First();
    Base<First>* f2 = new First();
    Base<Second>* s = new Second();
    f1->cmp(*dynamic_cast<First*>(f2)); // if this will throw, use RAII instead of deletes below
    // f1->cmp(*dynamic_cast<Second*>(f2)); error: cannot convert Second to First
    delete f1;
    delete f2;
}
person nurettin    schedule 09.12.2020
comment
С этим решением нет необходимости иметь cmp в качестве виртуального метода в Base. Следовательно, если единственная цель Base - предоставить cmp, тогда нет необходимости иметь Base вообще. С таким же успехом вы можете иметь First и Second как независимые классы, ни от чего не наследующие. - person n. 1.8e9-where's-my-share m.; 09.12.2020
comment
@ n.'pronouns'm. Я интерпретирую ... в вопросе как что-то большее в базовом классе - person Andreas; 09.12.2020