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

Этот вопрос относится к общим проблемам, обсуждаемым в следующих вопросах:

Могут ли виртуальные функции иметь параметры по умолчанию?

Параметры виртуальных функций по умолчанию

Вот что сейчас происходит в C ++ с параметрами по умолчанию для виртуальных функций:

struct Base
{
    virtual void foo(int one = 1, int two = 2)
            { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{
    virtual void foo(int one = 3, int two = 4) 
        { Base::foo(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

   b->foo();
   d->foo();
   dp->foo();

   return 0;
}

вывод:

one: 1 two: 2
one: 1 two: 2
 derived!
one: 3 two: 4
 derived!

Я хочу, чтобы такое поведение существовало в параметрах виртуальной функции C ++ по умолчанию:

#include <iostream>

using namespace std;

struct Base
{
    virtual void foo () { foo(1, 2); }
    virtual void foo (int one) { foo(one, 2); }
    virtual void foo(int one, int two)
            { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{   
    virtual void foo() { foo(3, 4); }
    virtual void foo(int one, int two) 
        { Base::foo(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

   b->foo();
   d->foo();
   dp->foo();

   return 0;
}

вывод:

one: 1 two: 2
one: 3 two: 4
 derived!
one: 3 two: 4
 derived!

Итак, в основном, если я хочу переопределить параметр по умолчанию в родительском классе, я просто создам новый foo с таким количеством аргументов. Обратите внимание, что производное переопределяет условие без аргумента, но не условие с одним аргументом. Кстати, в моем текущем проекте используется чисто виртуальный базовый класс. У меня часто есть указатели типа базового класса и типа производного класса. Я хочу, чтобы вызов любого указателя имел одинаковый результат.

ВОПРОСЫ:

Я прочитал много вопросов, связанных с этой темой, но, похоже, все они не дают разумных решений. Большинство решений приведут к более уродливому коду на всем протяжении вашего проекта.

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

Некоторые говорят, что в базовом классе есть только параметры по умолчанию, но это означает, что любой указатель на производный объект должен быть приведен обратно к базовому указателю, прежде чем можно будет использовать значения по умолчанию. Это также делает большой код уродливым.

Есть ли причины, по которым мне следует избегать вышеуказанного дизайна?


person Cory-G    schedule 05.08.2014    source источник
comment
Я не вижу здесь параметра по умолчанию. Не могли бы вы добавить его, чтобы мы точно знали, что вы имеете в виду?   -  person gexicide    schedule 05.08.2014
comment
@gexidide Параметры по умолчанию подделаны с использованием перегрузки, см. foo(), foo(one) и foo(one, two) в классе Base и foo() и foo(one, two) в классе Derived   -  person Cory-G    schedule 05.08.2014
comment
Но тогда это не параметры по умолчанию. Таким образом, утверждение «Не использовать параметры по умолчанию для виртуальных функций» больше не выполняется, поскольку здесь вы не используете параметры по умолчанию. Что не работает с вашим решением? Можете ли вы привести случай, когда будет получен неправильный результат?   -  person gexicide    schedule 05.08.2014
comment
@gexicide Этот вопрос относится к общей проблеме. Я понимаю, что многие могут не знать об этом обсуждении. См. Ссылки, которые я добавил в начало, для получения полного контекста.   -  person Cory-G    schedule 05.08.2014
comment
Все ссылки говорят о реальных параметрах по умолчанию, а не о поддельных. Поддельные - в отличие от настоящих - при правильном применении работают в полиморфном сценарии, поэтому я не вижу проблем с вашим дизайном.   -  person gexicide    schedule 05.08.2014
comment
Вы объединяете конструктор по умолчанию с параметром по умолчанию?   -  person John Dibling    schedule 05.08.2014
comment
@JohnDibling Нет. Чтобы более четко увидеть проблему с предложенным мной решением, см. Ссылку 2 выше, ответ 1.   -  person Cory-G    schedule 05.08.2014
comment
@CoryBeutler: Мне просто трудно понять, чего вы пытаетесь достичь. Вы упоминаете параметры по умолчанию, но нигде в вашем коде не упоминаются параметры по умолчанию.   -  person John Dibling    schedule 05.08.2014
comment
Вы хотите сказать, что хотите позвонить d->foo() и чтобы foo(3,4) действительно произошло?   -  person John Dibling    schedule 05.08.2014
comment
Если бы вы могли опубликовать желаемый результат cout, это могло бы помочь.   -  person John Dibling    schedule 05.08.2014
comment
@JohnDibling Я отредактировал ответ, чтобы включить текущую работу C ++ и желаемый результат.   -  person Cory-G    schedule 06.08.2014
comment
Пожалуйста, пожалуйста, добавьте обоснование, почему вам нужны другие параметры по умолчанию в производных классах. Спросите себя, моделируете ли вы все еще отношения.   -  person Martin Ba    schedule 06.08.2014


Ответы (3)


В какой-то момент будущие сопровождающие вашего кода будут сбиты с толку, сбиты с толку и / или озадачены, если вы измените значения по умолчанию в зависимости от того, какой статический тип они вызывают foo, поэтому я предполагаю, что это не ваша проблема.

Учитывая, что вас беспокоит то, что кто-то изменил значение по умолчанию в родительском классе и забыл обновить дочерний класс, что легко решается с помощью шаблона невиртуального интерфейса:

#include <iostream>

using namespace std;

struct Base
{
    void foo(int one = 1, int two = 2) { foo_impl(one, two); }

protected:
    virtual void foo_impl(int one, int two)
    { cout << "one: " << one << " two: " << two << endl; }
};

struct Derived : public Base
{   
protected:
    virtual void foo_impl(int one, int two)
    { Base::foo_impl(one, two); cout << " derived!" << endl; }
};

int main()
{
    Base* b = new Base();
    Base* d = new Derived();

    Derived* dp = new Derived();

    b->foo();
    d->foo();
    dp->foo();

    return 0;
}
person Mark B    schedule 05.08.2014
comment
Это решение было бы хорошо во многих случаях, но оно не дает того же результата, что и код желаемого результата в моем вопросе. Я хотел бы, чтобы производные классы могли переопределять параметры по умолчанию и использовать их независимо от методов вызова foo. Поскольку в моем текущем проекте есть чисто виртуальный базовый класс, я бы не возражал, если бы значения по умолчанию находились только в производных классах, а не в базовом классе вообще (по сути, делайте foo() и foo(one) в вопросе `= 0`.) Я буду тем не менее, проголосуйте за, так как это может помочь некоторым ищущим ответы. - person Cory-G; 05.08.2014
comment
@Cory Beutler Переопределение параметров по умолчанию в определенных дочерних классах только запутает пользователей и других разработчиков вашего кода. Просто не делай этого. Вы тратите дополнительные пять секунд на набор текста сейчас, чтобы явно передать свои параметры и сэкономить пять часов на отладку через шесть месяцев, когда кто-то не осознает, что значения по умолчанию различны для каждого уровня наследования. - person Mark B; 05.08.2014
comment
Если мне нужно явно передавать эти параметры каждый раз, когда я вызываю функцию, тогда эти значения по умолчанию меняются, мне пришлось бы находить каждый вызов в коде и изменять его в каждом случае, что для меня хуже. - person Cory-G; 05.08.2014
comment
@Cory Beutler Согласна, и единый четко определенный набор значений по умолчанию вполне хорош и удобен в обслуживании. Можете ли вы дать нам конкретный пример использования, когда разные дочерние классы нуждаются в разных значениях по умолчанию? Может быть, есть совершенно другой образ мышления для решения вашей основной проблемы. - person Mark B; 06.08.2014
comment
Я попробую, но его может нужно опубликовать в отдельном вопросе, так как этот вопрос сейчас довольно длинный и может иметь разные ответы. Я выложу здесь ссылку, если опубликую ее как отдельный вопрос. - person Cory-G; 06.08.2014

Я думаю, что проблема в «понимании того, что происходит» заключается в том, что значения по умолчанию для аргументов функции решаются во время компиляции - это означает, что, если это не ОЧЕНЬ очевидно и просто, компилятор не ЗНАЕТ, на какой класс указывает какой-либо указатель, и выиграл Не приведу верных аргументов. Другими словами, компилятор будет использовать класс указателя при определении аргументов для вашей функции. Нет абсолютно никакого способа обойти это (кроме «знания того, какой класс вы хотите использовать», но тогда довольно бессмысленно использовать виртуальные функции).

Решение зависит от того, что вы ДЕЙСТВИТЕЛЬНО хотите сделать, но очевидный ответ - НЕ использовать аргументы по умолчанию. Другое решение, как и у вас, - это своего рода косвенный вызов функции, где функция «без аргументов» отличается от функций с одним и двумя аргументами.

Но, возможно, также неплохо задаться вопросом, верен ли ваш дизайн с самого начала. Возможно, вам нужно где-то найти другое решение ...

person Mats Petersson    schedule 05.08.2014
comment
Только что вспомнил, что мой профессор в классе C ++, параметры функции вместе с именами функций компилируются в уникальную строку / подпись, поэтому я понимаю, что значения параметров по умолчанию определяются во время компиляции, и компилятор не будет знать, на какой класс указывает указатель пока еще. - person strisunshine; 18.05.2017
comment
В самом деле, компилятор не будет знать, какая функция будет вызвана до тех пор, пока он не скомпилирует код, поэтому не сможет здесь правильно использовать значения по умолчанию. - person Mats Petersson; 19.05.2017

Прежде всего, вы не используете параметры по умолчанию в своем примере. Таким образом, проблема с обычными параметрами по умолчанию не распространяется на ваш код. В вашем коде подклассы могут переопределять параметры по умолчанию, которые будут работать, как ожидалось, независимо от того, используется ли Derived через указатель Base или Derived. Таким образом, я не вижу проблемы в вашем подходе, мне он кажется нормальным.

person gexicide    schedule 05.08.2014