Chinaunix

标题: 请教个关于多态的问题 [打印本页]

作者: demilich    时间: 2015-08-18 15:27
标题: 请教个关于多态的问题
#include <iostream>
using namespace std;
  
class A
{
public:
     virtual void f()
     {
         cout << "A" << endl;
     }
};
  
class B: public A
{
public:
     virtual void f()
     {
         cout << "B" << endl;
     }
  
     void e()
     {
         cout << x << endl;
     }
private:
     int x;
};
  
int main()
{
     A *pa = new A();
     B *pb = (B*)pa;
     pb->f();
     pb->e();
}
  
输出结果A和0
问题:
1. pb->f(),一般我们调用都是 基类指针->派生类对象,反过来这么操作,一般应该用dynamic_cast对吧?但是就算不用dynamic_cast,用static_cast或者这种传统转型,也应该出错的吧 ...
2. pb->e(),这个就完全无法理解了,这是A的对象啊 ...
作者: MMMIX    时间: 2015-08-18 19:55
本帖最后由 MMMIX 于 2015-08-18 21:00 编辑
demilich 发表于 2015-08-18 15:27

  1. pb->f(),一般我们调用都是 基类指针->派生类对象,反过来这么操作,一般应该用dynamic_cast对吧?

没错, 而且应该写成类似如下的形式:

if (B *pb = dynamic_cast<B *>(pa)) {
    ...
} else {
    ...
}

但是就算不用dynamic_cast,用static_cast或者这种传统转型,也应该出错的吧 ...


都是指针, 而且你都做了强制类型转换了...

2. pb->e(),这个就完全无法理解了,这是A的对象啊 ...


pb->e() 是静态绑定, 根据 pb 的类型找到 e(); pb->f() 是动态绑定, 根据 pb 在运行时指向的对象找到 f().
作者: cokeboL    时间: 2015-08-18 20:17
多恶心的c++。。。我爱c版我不爱c++
作者: demilich    时间: 2015-08-18 21:25
回复 2# MMMIX


    那么问题在于,pb->e(); 里面x的值怎么回事?这个是A对象啊,没有x才对,为什么能打印出来?
作者: lxyscls    时间: 2015-08-18 21:27
本帖最后由 lxyscls 于 2015-08-19 09:29 编辑

回复 1# demilich
  1. #include <iostream>

  2. using namespace std;

  3. class A
  4. {
  5.     public:
  6.         virtual void f()
  7.         {   
  8.             cout << "A" << endl;
  9.         }   
  10. };

  11. class B: public A
  12. {   
  13.     public:
  14.         virtual void f()
  15.         {   
  16.             cout << "B" << endl;
  17.         }
  18.         
  19.         void e()
  20.         {   
  21.             cout << x << endl;
  22.         }
  23.     private:  
  24.         int x = 10;
  25. };

  26. int main(void)
  27. {
  28.     A *pa = new A;      
  29.     B *pb = (B*)pa;

  30.     pb->f();
  31.     pb->e();

  32.     B *pc = new B();
  33.     pc->e();

  34.     return 0;
  35. }
复制代码
输出是:
A
0
10

GDB:
  1. (gdb) b B::e
  2. Breakpoint 1 at 0x400b1a: file test.cpp, line 24.
  3. (gdb) r
  4. Starting program: /root/cpp/test
  5. A

  6. Breakpoint 1, B::e (this=0x603010) at test.cpp:24
  7. 24                              cout << x << endl;
  8. Missing separate debuginfos, use: debuginfo-install glibc-2.18-19.fc20.x86_64 libgcc-4.8.3-7.fc20.x86_64 libstdc++-4.8.3-7.fc20.x86_64
  9. (gdb) p &x
  10. $1 = (int *) 0x603018
  11. (gdb) x/10 0x603018
  12. 0x603018:       [color=Red]0[/color]       0       0       0
  13. 0x603028:       135137  0       0       0
  14. 0x603038:       0       0
  15. (gdb) c
  16. Continuing.
  17. 0

  18. Breakpoint 1, B::e (this=0x603030) at test.cpp:24
  19. 24                              cout << x << endl;
  20. (gdb) p &x
  21. $2 = (int *) 0x603038
  22. (gdb) x/10 0x603038
  23. 0x603038:       [color=Red]10[/color]      0       0       0
  24. 0x603048:       135105  0       0       0
  25. 0x603058:       0       0
  26. (gdb)
复制代码

作者: bskay    时间: 2015-08-19 09:05
pb->e()
其实是
pb->B::e()

实质是
B::e(pb)

类的成员函数,并不属于任何类的实例,而是独一无二的独立的函数
只不过编译器,把this指针隐式的传给它,也就是说语法上的B::e(),其实第一个参数是B*
.....看看python之类语法,就明白了
作者: littledick    时间: 2015-08-19 15:14
这样的强转越界访问,一旦发生bug,绝对会被交叉查错的同事874一百遍啊。
作者: windoze    时间: 2015-08-19 15:24
基类指针转型到子类指针不要用dynamic_cast,应该用static_cast,除非你不确定这个指针是不是真的指向一个子类对象。
作者: littledick    时间: 2015-08-19 15:36
windoze 发表于 2015-08-19 15:24
基类指针转型到子类指针不要用dynamic_cast,应该用static_cast,除非你不确定这个指针是不是真的指向一个子 ...

除非你有确定指针类型的方法,否则,天知道别人在调用你接口时传入的是不是那个子类型。
作者: windoze    时间: 2015-08-19 15:58
本帖最后由 windoze 于 2015-08-19 17:51 编辑

回复 10# littledick

转型前你总得猜一下吧,好多地方都有类似的东西:

  1. struct Base {
  2.     virtual int type_tag()=0;
  3. };

  4. struct Sub1 : public Base {
  5.     virtual int type_tag() { return 1; }
  6. };

  7. struct Sub2 : public Base {
  8.     virtual int type_tag() { return 2; }
  9. };

  10. ...
复制代码
然后用的时候就会这么写:

  1. void f(Base *p) {
  2.     switch(p->type_tag()) {
  3.     case 1: {
  4.         Sub1 *ps1=static_cast<Sub1 *>(p);
  5.         ps1-> ...
  6.     }
  7.     case 2: {
  8.         Sub2 *ps1=static_cast<Sub2 *>(p);
  9.         ps2-> ...
  10.     }
  11.     }
  12. }
复制代码
我知道这段代码很丑,不过重点不在这儿,而是说这段代码里就应该用static_cast,不应该用dynamic_cast

必须用dynamic_cast的地方是这样的:

  1. struct Base1 {
  2.     virtual ~Base1(){}
  3. };
  4. struct Base2 {
  5.     virtual ~Base2(){}
  6. };
  7. struct Sub : public Base1, Base2 { ... };

  8. void f(Base1 *pb1) {
  9.     Base2 *pb2=dynamic_cast<Base2 *>(pb1);
  10.     pb2->...
  11. }
复制代码
上面这段代码必须用dynamic_cast,否则就是错的。
作者: MMMIX    时间: 2015-08-19 16:44
回复 11# windoze


    dynamic_cast 说是在无法使用 virtual 函数的时候用, 能用 virtual 函数的时候应该交给 dynamic binding 去搞定.
作者: w_anthony    时间: 2015-08-20 14:52
1、这个情况dynamic_cast不是必须的,C方式转型通常能干除了dynamic_cast以外其他所有cast(大不了多转几次),而且转型没有“出错”的说法。dynamic_cast也只是转不了就是NULL,既不会编译“出错”,也不会链接“出错”,亦不会执行“出错”,除非你故意去访问错误指针里面的内容。
2、这个情况只是打印了错误对象中的x的值,由于子类比父类占用字节数较大,所以是越界访问了。由于这个越界访问没有写入过程,不会破坏内存块原有数据,通常是不容易出错的,除非这个对象刚好被分配到一页的最末尾,而下一页却不可读,这样越界了去读取不可读的内存地址才会出错。如果e函数里面有对x有写操作,由于只越界了4个字节,通常该地址也是可写的(除非也是一页最末尾而下页不可写),但是写入数据后将会破坏内存块原有数据内容。这样虽然这次不会立刻出错,但是隐患却一直存在,再在以后多次new、delete、malloc、free之后的某次如果需要依赖该块数据原有内容的时候就可能出错了,而且那时候再去查问题就很难再找到问题的源头。通常如果某个程序经常出现delete或free出错,new、malloc分配不出内存但实际可用内存还很多,那么一定是在这之前存在了一次对new或者malloc分配出的内存进行过越界写入操作。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2