Почему мой виртуальный вызов функции может быть неудачным? - PullRequest
1 голос
/ 10 апреля 2009

Обновление: Эта проблема вызвана неправильным использованием памяти, см. решение внизу.

Вот некоторый полупсевдокод:

class ClassA
{
public:
    virtual void VirtualFunction();
    void SomeFunction();
}

class ClassB : public ClassA
{
public:
    void VirtualFunction();
}

void ClassA::VirtualFunction()
{
    // Intentionally empty (code smell?).
}

void ClassA::SomeFunction()
{
    VirtualFunction();
}

void ClassB::VirtualFunction()
{
    // I'd like this to be called from ClassA::SomeFunction()
    std::cout << "Hello world!" << endl;
}

Эквивалент C # выглядит следующим образом: Удален пример C #, поскольку он не имеет отношения к реальной проблеме.

Почему функция ClassB::VirtualFunction не вызывается при вызове из ClassA::SomeFunction? Вместо этого ClassA::VirtualFunction называется ...

Когда я форсирую реализацию виртуальной функции ClassA :: VirtualFunction, примерно так:

class ClassA
{
public:
    virtual void VirtualFunction() = 0;
    void SomeFunction();
}

class ClassB : public ClassA
{
public:
    void VirtualFunction();
}

void ClassA::SomeFunction()
{
    VirtualFunction();
}

void ClassB::VirtualFunction()
{
    // I'd like this to be called from ClassA::SomeFunction()
    std::cout << "Hello world!" << endl;
}

Следующая ошибка возникает во время выполнения, несмотря на то, что деривативная функция была определенно объявлена ​​и определена.

pure virtual method called
terminate called without an active exception

Примечание: Кажется, что ошибка может быть вызвана даже плохим использованием памяти. Подробности смотрите в разделе самоответ.

Обновление 1 - 4:

Комментарии удалены (не опубликованы).

Решение:

Опубликовано в качестве ответа.

Ответы [ 8 ]

5 голосов
/ 10 апреля 2009
class Base {
public:
   virtual void f() { std::cout << "Base" << std::endl; }
   void call() { f(); }
};
class Derived : public Base {
public:
   virtual void f() { std::cout << "Derived" << std::endl; }
};
int main()
{
   Derived d;
   Base& b = d;
   b.call(); // prints Derived
}

Если в базовом классе вы не хотите реализовывать функцию, вы должны объявить так:

class Base {
public:
   virtual void f() = 0; // pure virtual method
   void call() { f(); }
};

И компилятор не позволит вам создать экземпляр класса:

int main() {
   //Base b; // error b has a pure virtual method
   Derived d; // derive provides the implementation: ok
   Base & b=d; // ok, the object is Derived, the reference is Base
   b.call();
}

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

4 голосов
/ 10 апреля 2009

Если вы получаете этот «чистый виртуальный метод, называемый сообщение об ошибке прекращения вызова без активного исключения означает, что вы вызываете виртуальную функцию из конструктора или деструктора класса A (базовый класс), чего не следует делать.

3 голосов
/ 10 апреля 2009

на чисто виртуальном методе, называемом error:

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

Не вызывать виртуальные функции из конструкторов или деструкторов

class Base
{
public:
   Base() { f(); }
   virtual void f() = 0;
};
class Derived : public Base
{
public:
   virtual void f() {}
};
int main()
{
   Derived d; // crashes with pure virtual method called
}

Проблема в приведенном выше коде состоит в том, что компилятор позволит вам создать экземпляр объекта типа Derived (поскольку он не является абстрактным: все виртуальные методы реализованы). Построение класса начинается со строительства всех основ, в данном случае Base. Компилятор сгенерирует таблицу виртуальных методов для типа Base, где запись для f () равна 0 (не реализована в base). Компилятор выполнит код в конструкторе. После того, как базовая часть была полностью построена, начинается строительство производной части элемента. Компилятор изменит виртуальную таблицу так, чтобы запись для f () указывала на Derived :: f () .

Если вы попытаетесь вызвать метод f () , пока еще создаете Base, запись в таблице виртуальных методов по-прежнему пуста, и приложение вылетает.

1 голос
/ 10 апреля 2009

Когда A вызывает VirtualFunction (), он автоматически вызывает версию на B. Это точка виртуальных функций.

Я не так хорошо знаком с синтаксисом C ++, хотя. Нужно ли объявлять функцию виртуальной в точке тела, а также в заголовке?

Alsop, в классе B вам, вероятно, нужно пометить его как override

в C # это просто. Я просто не знаю синтаксис C ++.

public class ClassA
{
    public **virtual** void VirtualFunction(){}

    public void FooBar()
    {
        // Will call ClassB.VirtualFunction()
        VirtualFunction();
    } 

}

public class ClassB
{
    public **overide** void VirtualFunction()
    {
        // hello world
    }
}
0 голосов
/ 11 апреля 2009

Чтобы вызвать функцию вирусной функции, вам нужно вызвать ее через указатель или ссылку.

void ClassA::SomeFunction()
{
    VirtualFunction();       // Call ClassA::VirtualFunction

    this->VirtualFunction(); // Call Via the virtual dispatch mechanism
                             // So in this case call ClassB::VirtualFunction
}

Вы должны иметь возможность различать два различных типа вызовов, в противном случае classA :: VirtualFunction () станет недоступным при переопределении.

Как указали другие, если вы хотите сделать версию базового класса абстрактной, тогда используйте = 0, а не {}

class A
{
    virtual void VirtualFunction() =0;
....

Но иногда вполне допустимо иметь пустое определение. Это будет зависеть от вашего точного использования.

0 голосов
/ 10 апреля 2009

В вашем коде нет ничего плохого, но ваш пример неполон. Вы не указываете, откуда вы звоните SomeFunction.

Как уже указывалось в dribeas, вы должны быть осторожны при вызове виртуальных функций из своего конструктора, поскольку виртуальные таблицы создаются только после завершения построения каждого класса в иерархии.

Редактировать: следующий абзац моего ответа был неверным. Извиняюсь. Можно вызывать SomeFunction из конструктора ClassB, поскольку виртуальная таблица находится (по крайней мере) в конце списка инициализатора, т. Е. Когда вы находитесь в теле конструктора. Конечно, это неправильно называть из конструктора ClassA.

Оригинальный абзац:

Я подозреваю, что вы должны вызывать SomeFunction из конструктора ClassB, после чего будет завершена только виртуальная таблица до типа ClassA, т. Е. С механизмом виртуальной диспетчеризации ваш класс по-прежнему имеет тип ClassA. Он становится объектом типа ClassB только после завершения конструктора.

0 голосов
/ 10 апреля 2009

Если вы хотите заставить производные классы реализовать VirtualFunction:

class ClassA
{
public:
    virtual void VirtualFunction()=0;
    void SomeFunction();
}

Это C ++. По умолчанию будет вызвана производная функция.

Если вы хотите вызвать функцию базового класса, выполните:

void ClassA::SomeFunction()
{
    // ... various lines of code ...

     ClassA::VirtualFunction();
}
0 голосов
/ 10 апреля 2009

Вы неправильно определяете функцию в ClassB, должно быть:

public class ClassB
{
    public void override AbstractFunction()
    {
        // hello world
    }
}

Тогда любой вызов из базового класса в виртуальные / абстрактные методы вызовет реализацию на производном экземпляре.

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