Подтвердить что ты не робот

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

У меня странная ситуация по следующему коду. Пожалуйста, помогите мне разобраться.

class B
{
       public:
            B();
            virtual void print(int data=10)
            {
                  cout << endl << "B--data=" << data;
            }
};
class D:public B
{
       public:
            D();
            void print(int data=20)
            {
                  cout << endl << "D--data=" << data;
            }
};

int main()
{
     B *bp = new D();
     bp->print();
return 0;
}

Что касается ожидаемого результата

[ D--data=20 ]

Но на практике это

[ D--data=10 ]

Пожалуйста, помогите. Это может показаться вам очевидным, но я не знаю внутреннего механизма.

4b9b3361

Ответ 1

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

Вызов, который вы написали в своем примере кода, сразу интерпретируется компилятором как bp->print(10) независимо от чего-либо еще.

Ответ 2

В стандарте говорится (8.3.6.10):

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

Это означает, что вы вызываете print через указатель типа B, он использует аргумент по умолчанию B::print.

Ответ 3

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

#include <iostream>
void frob (int x) {
    std::cout << "frob(" << x << ")\n";
}

void frob (int = 0);
int main () {
    frob();                     // using 0
    {
        void frob (int x=5) ;
        frob();                 // using 5
    }
    {
        void frob (int x=-5) ;
        frob();                 // using -5
    }
}

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

Ответ 4

Значение аргумента по умолчанию передается от имени вызывающего. С точки зрения вызывающего абонента он работает с классом B (не D), поэтому он передает 10 (как для класса B)

Ответ 5

ваша переменная имеет тип B, поэтому будет вызвана функция B. Чтобы позвонить D, вам нужно либо объявить свою переменную как D, либо передать в D.

Ответ 6

Динамическое связывание использует vpointer и vtable. Однако динамическое связывание применяется только к указателю на функцию. Механизм динамического связывания отсутствует.

Таким образом, аргумент по умолчанию определяется статически во время компилятора. В этом случае он статически определяется типом bp, который является указателем на базовый класс. Таким образом, data = 10 передается как аргумент функции, а указатель функции указывает на функцию класса Derived: D:: print. По сути, он вызывает D:: print (10).

Следующий фрагмент кода и результирующие результаты наглядно демонстрируют эту точку: даже если он вызывает функцию члена Derived: Derived:: resize (int), он передает аргумент по умолчанию базового класса: size = 0.

виртуальный void Derived:: resize (int) размер 0

#include <iostream>
#include <stdio.h>
using namespace std;

#define pr_dbgc(fmt,args...) \
    printf("%d %s " fmt "\n",__LINE__,__PRETTY_FUNCTION__, ##args);

class Base {
   public:
       virtual void resize(int size=0){
           pr_dbgc("size %d",size);
       }
};

class Derived : public Base {
   public:
       void resize(int size=3){
           pr_dbgc("size %d",size);
       }
};

int main()
{   
    Base * base_p = new Base;
    Derived * derived_p = new Derived;

    base_p->resize();           /* calling base member function   
                                   resize with default
                                   argument value --- size 0 */
    derived_p->resize();        /* calling derived member      
                                   function resize with default 
                                   argument default --- size 3 */

    base_p = derived_p;         /* dynamic binding using vpointer 
                                   and vtable */
                                /* however, this dynamic binding only
                                   applied to function pointer. 
                                   There is no mechanism to dynamic 
                                   binding argument. */
                                /* So, the default argument is determined
                                   statically by base_p type,
                                   which is pointer to base class. Thus
                                   size = 0 is passed as function 
                                   argument */

    base_p->resize();           /* polymorphism: calling derived class   
                                   member function 
                                   however with base member function  
                                   default value 0 --- size 0 */

     return 0;
}


 #if 0
 The following shows the outputs:
 17 virtual void Base::resize(int) size 0
 24 virtual void Derived::resize(int) size 3
 24 virtual void Derived::resize(int) size 0
 #endif