Какие функции отправляются динамически? - PullRequest
2 голосов
/ 05 июля 2011

Я искал вопросы, просматривал форумы, книги и т. Д. Я могу распознать полиморфное поведение методов, и есть много простых примеров, когда вызываемый метод определяется во время компиляции или выполнения.Но я был смущен этим кодом, где класс C наследует от B, который наследует от A:

class A {
protected:
    int x;

public: 
    virtual void change() = 0;
    virtual void change(int a) { x = a; }
};

class B : public A {
public: 
    void change() { x = 1; }
};

class C : public B {
public: 
    void change() { x = 2; }
    void change(int a) { x = a*2; }
};

int main () { 
    B *objb = new B();
    C *objc = new C();
    A *obja;
    objb->change();
    obja = objc;
    objc->change();
    obja->change(5);
    // ...
}

Многие примеры говорят (и это ясно), что происходит полиморфное поведение, и оно решается в runtime какой метод вызывать при выполнении следующей строки:

obja->change(5);

Но мои вопросы:

  1. Что происходит, когда я вызываю следующее (переопределено из чистого виртуального)?

    objb->change();
    
  2. Что происходит, когда я вызываю следующее (переопределено из виртуального, но не чистого)?

    objc->change(5);
    

Поскольку объявление класса переменных-указателей совпадает с объектами, следует ли определять вызов метода в compile или runtime ?

Ответы [ 3 ]

5 голосов
/ 05 июля 2011

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

Реальный вопрос: почему вас это волнует?Вы, очевидно, понимаете правила вызова виртуальных функций, а семантика поведения * всегда соответствует правилам диспетчеризации во время выполнения, поэтому не должно иметь значения, как вы пишете свой код.

3 голосов
/ 05 июля 2011

Есть три вопроса для рассмотрения.Первый - это разрешение перегрузки: в этом случае компилятор использует тип выражения static для построения набора функций, из которых он выбирает.Таким образом, если бы вы написали:

objb->change( 2 );

, код не скомпилировался бы, потому что нет change, который принимает int в области действия B.Если бы в области действия B вообще не было change, компилятор посмотрел бы дальше и нашел бы change (все они) в A, но как только он находит имя, он останавливается.

Это поиск имени и разрешение перегрузки функций, и оно полностью статично.

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

Наконец,возникает вопрос, используется ли динамическая диспетчеризация в сгенерированном коде.И это полностью зависит от компилятора.Компилятор может делать все, что захочет, при условии, что вызывается правильная функция, определенная двумя предыдущими проблемами.Обычно: если функция не виртуальная, динамическая диспетчеризация никогда не будет использоваться;и если доступ осуществляется непосредственно к объекту (именованному или временному объекту), динамическая диспетчеризация, как правило, не будет использоваться, поскольку компилятор может тривиально знать наиболее производный тип.Когда вызов осуществляется через ссылку или указатель, компилятор обычно использует динамическую диспетчеризацию, но иногда компилятор может отслеживать указатель в достаточной степени, чтобы знать тип, на который он будет указывать во время выполнения, и избегать динамической диспетчеризации.И хорошие компиляторы часто идут дальше, используя информацию профилировщика, чтобы определить, что в 99% случаев будет вызываться одна и та же функция, и вызов находится в тесном цикле и будет генерировать две версии цикла, одна с динамической диспетчеризацией.и один с наиболее часто вызываемой функцией inlined, и выберите, какая версия цикла через if, во время выполнения.

1 голос
/ 05 июля 2011
objb->change()

вызывает B::change(), потому что objb содержит адрес объекта типа B

objc->change(5);

, вызывает C::change(int), поскольку objc содержит адрес объекта типа C

Вызов метода будет по-прежнему динамический / время выполнения , поскольку методы B::change() & C::change(int) все еще являются виртуальными, поскольку виртуальный атрибут наследуется.

Чтобы ответить на вопрос, являются ли функции динамически отправляемыми или во время компиляции

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...