Случаи статического и динамического связывания в C++

Следующий код имеет 4 класса: Base1, Derived1 (производный от Base1), Base2, Derived2 (производный от Base2). Оба базовых класса имеют целочисленные функции data1 и display_data(). Оба производных класса имеют целые числа data1 и data2, а также функции display_data().

Я попробовал 4 случая в своем коде, которые можно увидеть в основной функции. Я не могу понять, какой из них является случаем статической привязки, а какой динамическим. Мне нужна помощь здесь. Я также хотел бы знать, какой из этих случаев можно считать переопределением функции.

КОД:

#include <iostream>
using namespace std;

class Base1{
protected:
    int data1;

public:
    Base1(int idata1 = 0) {
        data1 = idata1;
    }

    void display_data() {
        cout << "Base1: " << data1 << endl;
    }
};

class Derived1 : public Base1 {
protected:
    int data2;

public:
    Derived1(int idata1 = 0, int idata2 = 0) {
        data1 = idata1;
        data2 = idata2;
    }

    void display_data() {
        cout << "Derived1: " << data1 << ' ' << data2 << endl;
    }
};


class Base2 {
protected:
    int data1;

public:
    Base2(int idata1 = 0) {
        data1 = idata1;
    }

    virtual void display_data() {
        cout << "Base2: " << data1 << endl;
    }
};

class Derived2 : public Base2 {
protected:
    int data2;

public:
    Derived2(int idata1 = 0, int idata2 = 0) {
        data1 = idata1;
        data2 = idata2;
    }

    void display_data() {
        cout << "Derived2: " << data1 << ' ' << data2 << endl;
    }
};

int main()
{
    // case 1
    Derived1 d1(1, 10);
    d1.display_data();

    // case 2
    Base1* d2 = new Derived1(2, 20);
    d2->display_data();

    // case 3
    Derived2 d3(3, 30);
    d3.display_data();

    // case 4
    Base2* d4 = new Derived2(4, 40);
    d4->display_data();

    return 0;
}

ВЫВОД:

Derived1: 1 10
Base1: 2
Derived2: 3 30
Derived2: 4 40

person Amethyst    schedule 23.03.2021    source источник
comment
Ключевым здесь является спецификатор функции virtual. Когда функция-член не является virtual, используемая версия определяется во время компиляции на основе типа объекта, вызывающего функцию, поэтому в случае 2 вы видите, что вызывается Base1::display_data, потому что он вызывается из указателя Base1*. Когда используется функция virtual, используемая версия определяется во время выполнения на основе фактического наиболее производного типа объекта, который вы можете видеть в случае 4.   -  person Nathan Pierson    schedule 23.03.2021
comment
Когда вы читаете виртуальный, думайте о динамически связанном и переопределяемом.   -  person molbdnilo    schedule 23.03.2021


Ответы (1)


Вот моя попытка объяснить это простым способом :)

Статическая привязка возникает, когда объект связывается с функцией-членом на основе статического типа объекта (понимание типа его класса).

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

Прежде чем читать дальше: динамическое связывание работает только с указателями или ссылками и с виртуальными функциями для базового класса.

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

    Derived1 d1(1, 10);
    d1.display_data();

Вы знаете, что экземпляр d1 является автоматической переменной типа Derived1, и тогда он вызовет метод Derived1::display_data().

  • Первые условия не в порядке: d1 не является ни указателем, ни ссылкой.
  • второе условие не в порядке: Derived1::display_data не является виртуальным.

Для второго звонка

    Base1* d2 = new Derived1(2, 20);
    d2->display_data();

Мы видим, что объявленный тип переменной d2 относится к Base1, но экземпляр относится к Derived1 (это правильно из-за наследования, таким образом, Derived1 является классом Base1). Но вы еще не знаете, является ли метод display_data, который он вызовет, методом из Base1::display_data или методом из Derived1::display_data.

  • Первые условия в порядке, потому что у нас есть d2 указателя типа Base1*.
  • Второе условие не подходит, потому что Base1::display_data не является виртуальным. Таким образом, это все еще статическая привязка, тогда будет вызываться функция с объявленным типом, поэтому код вызовет Base1::display_data

На третий звонок

    // case 3
    Derived2 d3(3, 30);
    d3.display_data();

Это приведет к статической привязке, а затем вызову Derived3::display_data

  • Первое условие не в порядке: d3 не является ни указателем, ни ссылкой.
  • второе условие в порядке: Derived2::display_data является виртуальным.

На четвертый звонок

    Base2* d4 = new Derived2(4, 40);
    d4->display_data();

На этот раз это динамическая привязка.

  • Первые условия в порядке: d4 — указатель.
  • второе условие в порядке: Derived2::display_data является виртуальным. таким образом, вместо вызова метода из объявленного типа base2 он вызовет метод из объявленного экземпляра во время выполнения Derived2::display_data
person Pat. ANDRIA    schedule 23.03.2021
comment
Есть ли практический способ узнать, использует ли определенная часть кода статическую или динамическую привязку? Что-то вроде функции ide или некоторых операторов печати, чтобы их можно было использовать для любых других возникающих случаев. Или мы должны понимать это так, как вы объяснили? Кроме того, не могли бы вы уточнить, все ли эти случаи считаются переопределением метода? Я могу запутаться в его определении из-за множества ресурсов, доступных в Интернете. Это связано с тем, что он виртуальный? Спасибо за такой подробный ответ. Это проясняет концепцию статического и динамического связывания. :) - person Amethyst; 23.03.2021
comment
Практический способ — найти ключевое слово virtual, как сказал @molbdnilo. Вы можете прочитать, почему destructorдолжен быть virtual или нет, и лучше понять, почему метод также должен быть виртуальным. Действительно, динамическое связывание используется посредством наследования (переопределения) виртуального метода. Переопределение метода — это факт переопределения метода базового класса в методе производного класса, но изменение его определения (именно то, что вы сделали для производных классов). У вас может быть краткая литература здесь - person Pat. ANDRIA; 23.03.2021