Почему аргумент функции производного класса принимает значение аргумента функции базового класса?

Я работаю на С++. Ниже приведен мой код:

#include <iostream>
using namespace std;
class base
{
        public:
        virtual void display(int a = 4)
        {
                cout << "base ::  "<<  a*a << endl;
        }
};

class derived : public base
{
        public:
        void display(int b = 5)
        {
                cout << " Derived :: " << b*b*b <<  endl;
        }
};

int main()
{
        base *bobj;
        derived dobj;
        bobj = &dobj;
        bobj->display();
        return 0;
}

Результат:

Derived :: 64

Вызывается функция базового класса, но используется значение по умолчанию параметра производной функции. Почему метод производного класса display() принимает значение аргумента метода базового класса?


person BSalunke    schedule 03.09.2012    source источник


Ответы (5)


Потому что вы вызываете его через указатель на base. Вот как это работает.

Аргументы помещаются в стек аргументов (или внутрь регистров) перед фактическим вызовом. Поскольку у вас есть указатель на base и нет параметров, в функцию передается значение по умолчанию 4. Затем вызывается правильная функция (derived::display), но с параметром по умолчанию base. Конечно, это детали реализации, но поведение стандартное.

C++03 8.4/10

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

Я бы сделал акцент на цитате, но все это довольно очевидно.

dobj.display();

напечатает 125 (5^3).

person Luchian Grigore    schedule 03.09.2012

Аргументы по умолчанию вставляются вызывающей стороной. Ваш код эквивалентен

class base {
public:
    virtual void display(int a) { cout << "base ::  "<<  a*a << endl; }
    inline void display(void) { display(4); }
};

и т. д.

При вызове через указатель base вставляется значение по умолчанию из базового класса.

person Simon Richter    schedule 03.09.2012
comment
Мне нравится этот эквивалент, однако, чтобы понять, что происходит, нужно понять, как работает скрытие. - person Matthieu M.; 03.09.2012

Стандарт говорит сам за себя:

(§8.3.6/10) Вызов виртуальной функции (10.3) использует аргументы по умолчанию в объявлении виртуальной функции, определяемой статическим типом указателя или ссылки, обозначающей объект. Переопределяющая функция в производном классе не получает аргументы по умолчанию от переопределяемой функции. [ Пример:

    struct A {
      virtual void f(int a = 7);
    };
    struct B : public A {
      void f(int a);
    };
    void m() {
      B* pb = new B;
      A* pa = pb;
      pa->f();   // OK, calls pa->B::f(7)
      pb->f();   // error: wrong number of arguments for B::f()
    }
    — end example ]
person jogojapan    schedule 03.09.2012

Сделайте себе менее надуманный тестовый сетап, и станет понятно:

#include "base.hpp"

int compute(base * p)
{
    return p->display();
}

Теперь очевидны две вещи:

  1. Аргумент по умолчанию может исходить только из аргумента по умолчанию, указанного в base.

  2. Фактическая диспетчеризация является динамической, поскольку display является виртуальной функцией-членом.

person Kerrek SB    schedule 03.09.2012
comment
(1) не полностью для меня очевиден (я имею в виду, я знаю, что это правда, но если бы я не знал, то не мог бы сделать вывод из вашего кода). В некоторых языках аргументы по умолчанию не заполняются до тех пор, пока не будет введен вызываемый объект, поэтому виртуальные функции могут иметь переопределенные значения по умолчанию, как и ожидал вопрошающий. C++ просто не один из этих языков. - person Steve Jessop; 03.09.2012
comment
@SteveJessop: для 1: код даже не знает о существовании производных классов. Во всей единице перевода не упоминается никакое другое значение по умолчанию, кроме значения в base. Это то, что я имел в виду под очевидным (хотя, конечно, это должно быть очевидным для C++). Или, другими словами, разрешение перегрузки происходит статически. - person Kerrek SB; 03.09.2012
comment
@KerrekSM: да, вот почему для нас это очевидно, ключевой момент в том, что вызывающая сторона предоставляет значение по умолчанию. Если бы вы не знали этого факта о C++, то это не было бы очевидным — представьте соглашение о вызовах для языка, отличного от C++, в котором вызывающий вызывает виртуальную функцию с флагом, чтобы сказать, используйте значение по умолчанию для этого параметр Тогда разрешение перегрузки все еще может происходить статически, и вызывающий будет знать, что существует есть значение по умолчанию, но ему не нужно знать его значение. - person Steve Jessop; 03.09.2012

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

Как говорится в спецификации...

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

person Bharat Sinha    schedule 03.09.2012