Как работает приведенная ниже программа на С ++? - PullRequest
1 голос
/ 02 апреля 2010

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

Я не понимаю этого?

#include<iostream>

using namespace std;

class Animal
{
public:
  void talk()
  {
    cout<<"I am an animal"<<endl; 
  }
};

class Dog : public Animal
{  
public:
  void talk()
  {
    cout<<"bark"<<endl; 
  }
};

int main()
{
  Animal * a;
  Dog * d;

  d->talk();
  a->talk();  
} 

Ответы [ 7 ]

10 голосов
/ 02 апреля 2010

А) Это неопределенное поведение. Может произойти любое поведение.

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

В C ++ вызов метода-члена эквивалентен (на практике, если не определению) вызову члена со скрытой переменной this. Если метод виртуальный, он должен пройти через vftable, но не для не виртуального метода.

So

Foo::Bar(){}

является грубым эквивалентом

Foo_Bar(Foo *this){}

и в коде вызова

Foo *foo = new Foo();
foo->bar();

вторая строка примерно соответствует моральному эквиваленту

Foo_Bar(foo);

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

Но, учитывая предыдущее, посмотрите на реализацию:

void Foo::Bar(){printf("Hello, world!\n");}

и телефонный код:

Foo *foo = 0;
foo->Bar();

Как мы уже говорили, это примерно эквивалент (так как мы не виртуальные):

Foo *foo = 0;
Foo::Bar(foo);

Это означает, что мы вызываем эквивалент следующего метода:

void Foo_Bar(Foo* this)
{ printf("Hello, world\n"); }

Теперь, учитывая этот метод, мы на самом деле не разыменовываем указатель this! Итак, совершенно ясно, почему, в этом случае, метод будет работать и не потерпит неудачу.

Практическим результатом этого является то, что вызов не виртуальных методов для нулевого указателя, когда метод не разыменовывает член, обычно приводит к такому наблюдаемому поведению. Но полагаться на любое неопределенное поведение - это, в основном, зло.

8 голосов
/ 02 апреля 2010

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

0 голосов
/ 02 апреля 2010

Вы вызываете этот нестатический метод-член, вы должны создать объект класса, поэтому вам нужно:

 Animal * a =  new Animal();
 Dog * d = new Animal();

 d->talk();
 a->talk(); 

 delete a;
 delete d;

И чтобы использовать полиморфизм, вы должны использовать виртуальное ключевое слово перед talk ()

public:
virtual void talk()
  {
    cout<<"I am an animal"<<endl; 
  }
0 голосов
/ 02 апреля 2010

Это неопределенное поведение, так что может случиться что угодно.

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

Если ваш компилятор не указывает где-либо подобное поведение (чего, скорее всего, не будет), вы, конечно, не можете рассчитывать на это.

0 голосов
/ 02 апреля 2010

Мне кажется, причина того, что ваш код работает, заключается в том, что методы talk () фактически не обращаются ни к каким переменным-членам класса. Другими словами, вы на самом деле не обращаетесь к неявному этому , что оказывается недействительным.

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

Я не уверен, является ли это стандартным поведением или специфичным для компилятора. В моем случае я использовал Microsoft Visual Studio 2008.

0 голосов
/ 02 апреля 2010

Вам нужно изменить:

void talk()

Кому:

virtual void talk()

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

Animal* a = new Animal;
Dog* d = new Dog;

Не забудьте освободить их, прежде чем вернуться:

delete a;
a = 0; 

delete d;
d = 0;

На практике вы захотите использовать boost::shared_ptr или std::auto_ptr как в:

std::auto_ptr<Animal> a(new Animal);
std::auto_ptr<Dog> b(new Dog);

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

Animal a;
Dog d;

С учетом вышеизложенного вы бы использовали a.talk() и d.talk() вместо a->talk() и d->talk().

0 голосов
/ 02 апреля 2010

Вам необходимо использовать оператор new.

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