Функция с производными аргументами в C ++ - PullRequest
0 голосов
/ 08 апреля 2020

Допустим, у меня есть следующие классы:

class A {
};

class B : public A {
};

class C : public A {
};

class BaseClass {
public:
    virtual void dummy(A* a) = 0;
};

class DerivedClass1 : public BaseClass {
public:
    void dummy(B* b);
};

class DerivedClass2 : public BaseClass {
public:
    void dummy(C* c);
};

Как видите, B и C наследуют от A, а DerivedClass1 и DerivedClass2 наследуют от BaseClass.

Из-за моего понимания такое объявление функции dummy() недопустимо, поскольку аргументы изменились, хотя B и C получены из A.

Как правильно это сделать?

Можно было бы оставить аргументы такими, какими они были в BaseClass, и просто потом выполнять приведение в функции dummy, но я думаю, это не очень хорошо.

В противном случае, просто не производите dummy (и удалите его из BaseClass), поэтому оба класса DerivedClass1 и DerivedClass2 объявляют их независимо. Но тогда идея всех классов, полученных из BaseClass, должна реализовывать пустышку, теряется.

Ответы [ 4 ]

1 голос
/ 08 апреля 2020

Прежде всего, когда объявляется, что что-то A является базовым классом B или C, вы делаете это с намерением, чтобы B и C имели все свойства A и что вы можете использовать B и C в любом месте, где вы можете использовать A дюйма. Хороший дизайн часто дает содержательное определение A, которое допускает общее c определение dummy для всех специализации A. В этом смысле проблема может быть менее распространенной, чем вы можете ожидать.

Если вы хотите создать экземпляр DerivedClass1 или DerivedClass2, ваше понятие DerivedClass1 и DerivedClass2 на самом деле не имеет смысла, потому что для того, чтобы было a BaseClass, они должны дать содержательное определение void dummy(A *a). Вы должны предоставить это определение. Они могут предоставлять дополнительные методы void dummy(B *b) или void dummy(C *c), но они используются только в том случае, если тип BaseClass реализации *1027* stati является DerivedClass1 или DerivedClass2.

Если DerivedClass1 или DerivedClass2 необходимо различать guish между B и C, это может быть признаком плохого дизайна. Но это может быть и обоснованное решение. Как правило, вам нужен способ динамической отправки на правильный метод. Например, это может быть достигнуто следующим образом:

class DerivedClass1 : public BaseClass
{
    virtual void dummy( A * a ) override {
        if( dynamic_cast<B*>( a ) != nullptr ) {
            dummy( dynamic_cast<B*>( a );
        }
        else if( dynamic_cast<C*>( a ) != nullptr) {
            dummy( dynamic_cast<C*>( a );
        }
        else
        {
            // ...
        }
    }

    void dummy( B * );
    void dummy( C * );
};
0 голосов
/ 08 апреля 2020

C ++ не может обработать вашу ситуацию, потому что нет никакого способа наложить такое ограничение на вызовы функций базовым классом. Но изменение типа виртуальной функции может быть сделано, например, для возвращаемых типов, см. ковариация и контравариантность :

class BaseClass {
public:
    virtual void dosomething(A*) = 0;
    virtual A* dummy(A*) = 0;
};

class DerivedClass1 : public BaseClass {
public:
    void dosomething(A* a) {B* b = dummy(a); do something;}
    //See return type was A* but now is B*
    B* dummy(A* a) { return dynamic_cast<B*>(a); }
};
0 голосов
/ 08 апреля 2020

Смысл виртуального наследования заключается в том, что вы можете вызывать ваши производные классы через ссылку / указатель на базовый тип:

BaseClass* p = some_factory();

Следовательно, при вызове p.dummy(...) вы (не обязательно) знаете, что Тип времени выполнения сидит за указателем. Таким образом, вы не можете разумно решить, должны ли вы передать ему экземпляр A, B или C.

Вот почему вы должны сопоставить тип объявления и выяснить в вашей переопределяющей функции, что на самом деле объект у вас есть. В зависимости от контекста, возможно,

  • static_cast: если вы точно знаете, что вызывающий абонент передаст вам правильный тип времени выполнения
  • dynamic_cast: если вы этого не сделаете , но это влечет за собой затраты и требования времени выполнения rtti
  • или вашего типа A имеет виртуальные функции-члены, которые вы можете просто вызывать через аргумент A*

Например:

void DerivedClass1::dummy(A* a) {
  if (B* const b = dynamic_cast<B*>(a)) {
    /* do stuff with b */
  } else {
    /* error handling -> wrong runtime type */
  }
}
0 голосов
/ 08 апреля 2020

Из-за моего понимания такое объявление функции dummy () недопустимо

Технически показанные объявления dummy разрешены. Однако, возможно, вы надеялись, что дочерние элементы будут конкретными, а это не так.

В противном случае, просто не производите фиктивную переменную (и удалите ее из BaseClass), поэтому оба класса DerivedClass1 и DerivedClass2 объявляет их независимо.

Этот подход имеет смысл.

Но тогда идея всех классов, производных от BaseClass, должна реализовывать пустышку, теряется.

Это просто идея, которую нельзя выразить наследованием базового класса. Не существует понятия «дочерний элемент реализует функцию-член с аргументом some ». Тип аргумента должен быть известен, и все дочерние элементы должны принимать все аргументы, которые принимает база. И это не выполняется вашей иерархией, поэтому дети остаются абстрактными.

Структура, которую вы ищете вместо этого, кажется концепцией . C ++ 20 представит программный c способ определения понятий; до тех пор понятия неявны: если вы пишете шаблон, вы можете указать, что аргумент типа должен удовлетворять требованию наличия функции-члена dummy. Оба «производных» типа будут соответствовать этой концепции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...