Динамическое связывание внутри конструктора для виртуальной функции в C ++ - PullRequest
0 голосов
/ 16 ноября 2018

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

В этом случае, если раннее связываниеиспользуется внутри моего базового конструктора, я передал производный объект в указатель базового класса, который является полностью приемлемым (здесь выполняется обновление).Если используется раннее связывание, выбор виртуальной функции должен основываться на типе указателя (здесь Base *), а не на содержимом указателя (объект, на который указывает указатель, потому что мы не знаем точный объектбудучи указанным).В этом случае, поскольку тип указателя - Base *, мы должны были вызывать только виртуальную функцию базового класса в обоих случаях.Может, кто-нибудь прояснит это?

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

Первая строка вывода, которая вызывает базу, полностью в порядке

class Base
    {
        public:
        Base(){
            fun();
        }
        Base(Base *p)
        {
            p->fun();
        }
        virtual void fun()
        {
            cout<<"In Base"<<endl;
        }
    };

    class Derived : public Base
    {
        public:
        void fun()
        {
            cout<<"In Derived"<<endl;
        }
    };

    int main()
    {
        Derived d;
        Base b(&d);
    }

O / P:

In Base
In Derived

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

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

  • В конструкторе механизм виртуального вызова отключен, поскольку переопределение из производных классов еще не выполненополучилось.Объекты создаются с нуля, «база перед производным».

  • Уничтожение выполняется «производным классом перед базовым классом», поэтому виртуальные функции ведут себя так же, как в конструкторах: только локальные определенияused - и никакие вызовы не переопределяют функции, чтобы избежать прикосновения к (теперь разрушенной) части производного класса объекта.

0 голосов
/ 16 ноября 2018

Правило для виртуальных вызовов в теле конструктора применяется к объекту , который в настоящее время создается , поскольку этот объект еще не считается объектом какого-либо производного класса. (И есть аналогичное правило для объекта, который в настоящее время уничтожается.) Он имеет мало общего с «ранним связыванием» в смысле использования типа времени компиляции, такого как синтаксис ClassName::member_func() force.

В вашем коде два разных объекта d и b, а конструктор d полностью завершен к тому времени, как вы доберетесь до строки p->fun();.

Подробно:

  1. В программу входит main.
  2. Объект d создается с помощью неявно объявленного конструктора по умолчанию Derived.
  3. Первое, что Derived::Derived() делает, это создает подобъект базового класса, вызывая конструктор по умолчанию Base::Base().
  4. Тело Base::Base() вызывает fun(). Поскольку мы еще не вошли в тело конструктора Derived::Derived(), этот виртуальный поиск вызывает Base::fun().
  5. Base::Base() заканчивается.
  6. Тело Derived::Derived(), которое пусто, выполняется и завершается.
  7. Возвращаясь к main, объект b создается путем передачи указателя на d в конструктор Base::Base(Base*).
  8. Тело Base::Base(Base *p) вызывает p->fun(). Поскольку p является указателем на d, который уже является полностью сконструированным объектом типа Derived, этот виртуальный поиск вызывает Derived::fun().

Сравните этот немного другой пример, в котором мы определяем конструктор по умолчанию Derived для передачи this (неявно преобразуемого в Base*) в конструктор для подобъекта Base. (Это действительно, хотя может быть рискованно, если указатель использовался другими способами, например, в инициализаторе для базы или члена Base.)

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    Base(){
        fun();
    }
    Base(Base *p)
    {
        p->fun();
    }
    virtual void fun()
    {
        cout<<"In Base"<<endl;
    }
};

class Derived
{
public:
    Derived() : Base(this) {}
    virtual void fun() override
    {
        cout << "In Derived" << endl;
    }
};

int main()
{
    Derived d;
}

Эта программа будет печатать только «In Base», поскольку теперь в Base::Base(Base *p), p указывает на тот же объект, который в данный момент создается.

...