【问题标题】:How does the below program work in c++?以下程序如何在 C++ 中工作?
【发布时间】:2010-04-02 04:04:14
【问题描述】:

我刚刚创建了 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();  
} 

【问题讨论】:

标签: c++ class pointers


【解决方案1】:

A) 这是未定义的行为。任何行为都可能发生。

B)由于您没有调用虚拟方法,因此很容易解释为什么未定义的行为实际上会这样做(并且我已经在几乎所有我能找到的编译器下对此进行了测试)。

在 C++ 中,调用成员方法等同于调用具有隐藏“this”变量的成员(实际上,如果不在定义中)。如果方法是虚拟的,它必须通过vftable,但不是非虚拟方法。

所以

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 指针!所以,很清楚为什么在这种情况下,该方法会起作用并且不会失败。

这样做的实际结果是,在空指针上调用非虚拟方法(该方法不会取消引用 a 成员)通常会导致这种观察到的行为。但是,依赖任何未定义的行为基本上都是邪恶的。

【讨论】:

    【解决方案2】:

    当您做的事情有未定义的行为时,任何事情都可能发生——包括它看起来有效。在这种情况下,您的情况似乎就是这样。

    【讨论】:

    • +1 以上评论和回答。当然,我们不能确定出现的是什么:P
    • 没错。它在我的编译器中工作,但我不明白它在什么情况下可以工作?
    • @Srinivasa:在 no 情况下,未定义的行为会起作用。 。在任何情况下,它可能似乎工作,但仅此而已。
    • @Srinivasa,您必须查看程序的二进制反汇编才能获得更多信息。如果可以的话,也许查看编译器的源代码会对您有所帮助。我最好的猜测是,这似乎可行,因为所讨论的方法不以任何方式依赖于对象特定实例的详细信息。编译器可能会将这些调用更改为简单的函数调用,并忽略它们被用于未初始化指针的事实。
    • +1。虽然我的回答详细说明了为什么会观察到特定的未定义行为,但您的回答(未定义,不要这样做)是明确的答案。
    【解决方案3】:

    您需要使用new 运算符。

    【讨论】:

    • @Travis G:实际上,我认为在 C++ 中以这种方式隐藏继承的名称是合法的——尽管不可否认,这是一种不好的做法,并且不会给习惯 Java 的人带来他们期望的东西 ;)
    • @BillyONEal:你是对的;使用名称隐藏不会使程序运行慢得离谱。 :-D(我开玩笑!我开玩笑!有点……)
    • @James McNellis:开玩笑是完全可以的,只要你明白不会有独角兽在吃东西:P
    • 抱歉这个简洁的问题。当我运行这个程序并期望它不起作用时,它出乎意料地起作用了!!!我知道我想要做的是未定义的行为,但谁能解释在什么情况下“未定义”的行为会起作用?
    【解决方案4】:

    你需要改变:

    空谈()

    收件人:

    虚空谈话()

    如果你希望函数是多态的。此外,您需要实例化您的对象,如下所示:

    动物* a = 新动物; 狗* d = 新狗;

    在返回之前不要忘记释放它们:

    删除一个; a = 0; 删除 d; d = 0;

    在实践中,您可能希望使用boost::shared_ptrstd::auto_ptr,如下所示:

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

    以上将节省您调用删除的需要。或者,您可以使用自动存储:

    动物一个; 狗 d;

    使用上述方法,您将使用 a.talk()d.talk() 而不是 a-&gt;talk()d-&gt;talk()

    【讨论】:

    • 修正了删除的拼写错误。不过附注——你真的应该使用 NULL 而不是 0 来设置指向 NULL 的指针(或者 nullptr 如果你有幸拥有一个支持它的编译器)
    • auto_ptr?呸。请至少scoped_ptrshared_ptr :)
    • @BillyONeal,我不同意... NULL 你必须包含 ... 使用 0 不需要任何包含。在 C++0x 中,尽管 nullptr 是理想的。
    • @Billy,我同意 auto_ptr... 但在 std::shared_ptr 和 std::scoped_ptr 成为标准之前,我将参考 Boost (boost::shared_ptr 和 boost::scoped_ptr) 和std::auto_ptr(对于那些只想坚持使用标准库的人)。
    • @Billy:我很好奇你的理性。
    【解决方案5】:

    我相信您的代码有效的原因是 talk() 方法实际上并未访问该类的任何成员变量。换句话说,您实际上并没有访问隐含的 this,它恰好是无效的。

    我之前确实遇到过同样的问题。我的代码正在调用空指针的成员函数,并且它可靠地工作。当我修改函数以便它实际尝试访问成员变量时,我终于发现了这个问题。在那次更改之后,它可靠地崩溃了。

    我不确定这是标准行为还是特定于编译器的行为。就我而言,我使用的是 Microsoft Visual Studio 2008。

    【讨论】:

    • 这不是标准行为,它是未定义的行为。如果您的编译器选择让它看起来可以工作,那取决于它的实现者。
    【解决方案6】:

    这是未定义的行为,所以任何事情都可能发生。

    它可能只是打印正确的东西,因为方法不访问它们被调用的对象的任何成员变量(不需要访问对象应该存在的内存,因此访问冲突不一定会发生)。

    除非您的编译器在某处指定了这种行为(它很可能不会),否则您当然不能指望这种行为发生。

    【讨论】:

      【解决方案7】:

      你调用这个非静态成员方法,你应该构造类的对象,所以你需要:

       Animal * a =  new Animal();
       Dog * d = new Animal();
      
       d->talk();
       a->talk(); 
      
       delete a;
       delete d;
      

      要使用多态,你应该在talk()之前使用virtual关键字

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

      【讨论】:

        猜你喜欢
        • 2019-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-21
        • 2018-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多