聊一下C++给自己挖的坑~~~

redleaves 2012-08-19 02:37:08
C++标准中明确说,通过"指针"或"引用"使用对象的虚函数,对象有动态行为.
通过对象本身直接调用虚函数则不在此列.它直接调用虚函数的final overrider.

也就是说,
A a;
// 假设foo是虚函数
a.foo() // 这时没有动态行为
A *pa = &a;
pa->foo(); // 这时有动态行为

那如果
(&a)->foo(); // 这样会怎么样?
从语法上来讲,这是希望通过指针使用对象的虚函数.但几乎所有编译器把这句都优化成为
a.foo();
从结果来看,这样无法使用对象的动态行为.(不过GCC,CLANG在某些上下文中,使用O0优化可以得到动态行为)
这和标准是有冲突的.因为我没能在标准里找到关于这种优化的明确说明.
难道这是从this行为反推出来的?
在标准里,讲虚函数行为时,有指出,省略this的虚成员调用具有动态行为.而在说成员函数调用时,又指出,省略this
的成员函数调用相当于(*this).member这种形式.联系成员的动态行为,推出(*this).member等价于this->member.
于是乎,反推出(&obj)->func等价于obj.func
在现实中,也确实是这样,几乎所有的编译器都使用这种行为.

大家都可以看出来,在这种上下文中,a.foo无论动态/静态,都没有关系.因为这里名称a是直接绑定到对象的.动态行为
实际上就是静态行为.我猜当初标准的设计也是这么想的,所以a.foo这类调用被赋予了静态行为,从而更高效.否则也
没必要发明final overrider这个概念了.
现在问题来了,标准中有这么一种用法
A *pa;
...
pa->~A();
new(pa) B;
手工析构一个对象,然后在那个对象的地址上重新创建另一个对象.只要对象的大小能保证,行为是明确的.
如果用这种办法,把前面例子中的a变成它的派生类B的对象,问题就来了.
这里a绑定的对象,实际已经是B了.但a.foo还是具有静态的行为,pa->foo却会有动态行为.一些"聪明"的编译器发现pa直接
引用自a,还会自动把pa->foo变成了a.foo ...
由于栈中对象的析构过程是静态绑定的,所以,就算A有虚析构函数也不顶事.a在从栈中析构时,还是被当做A的实例,而非B.

假如a.foo同样是动态调用,以上所有的问题就迎刃而解了.也没必要为了统一静态动态行为而发明final overrider,以及
与其配套的相关规则了.反正都没有完全统一.结果这种虚函数静态调用机制,既和虚函数动态调用机制行为不同,又和非虚
函数静态调用机制不同(这个不同请参考using不覆盖final overrider的规则).

果然过早的优化是一种罪恶~~
...全文
153 6 打赏 收藏 转发到动态 举报
写回复
用AI写文章
6 条回复
切换为时间正序
请发表友善的回复…
发表回复
竞天问 2012-08-19
  • 打赏
  • 举报
回复
最后的问题可以用“剪裁”解释不?
www_adintr_com 2012-08-19
  • 打赏
  • 举报
回复
现在问题来了,标准中有这么一种用法
A *pa;
...
pa->~A();
new(pa) B;
====================================
没有看出哪个标准中说了在调用完对象的析构函数后还能再去调用这个对象的其他方法!
用使用你新构造出来的对象, 你必须通过 B* pb = new(pa) B; 的 pb 指针来使用新的对象. 虽然它的内存没有释放, 但是老的 pa 对象是已经不存在了的. 这和 delete pa 后再去调用 pa 的方法一样, 没有哪个标准中有.
www_adintr_com 2012-08-19
  • 打赏
  • 举报
回复
1. 虚函数调用对性能的影响并不是微乎其微的. 尤其是在很多小函数加递归的调用中, 函数调用的开销有可能比函数本身的代码都大.
2. 如果你所说的问题就是:
A *pa;
...
pa->~A();
new(pa) B;

这个根本和什么动态绑定静态绑定没有关系, 这根本就是错误的用法, 你这样用也是符合语法的:
A *pa;
...
pa->~A();
new(pa) int;

那之后的 pa 就是一个 int, 上面任何方法都没有, 但是你还可以通过 pa 的指针去调用 A 的方法, 不管是静态还是动态都是错误的. 所以这个问题就是一个不匹配类型之间的强制转换问题, 和什么动态绑定, 静态绑定一点关系都没有.


redleaves 2012-08-19
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 的回复:]你这些推来推去都没有什么道理可言, 编译器优化是要根据上下文来的, 不是指根据某一条语句就能做优化的. 在某个地方的 (&a)->foo(); 被优化成了静态绑定, 并不代表所有的 (&a)->foo(); 语句都变成静态绑定了. 编译器在不改变代码行为的情况下可以做任何的优化, 甚至 (&a)->foo(); 优化来没有函数调用也没什么错, 只要优化后的代码产生的结果和不优化产生的结果一样就行.[/Quote]真是晕倒,难道是我中文水平有问题?
我说的是,因为编译器的行为和标准里说的不同,而且是多数厂商集体这么干.所以我推测他们为什么会这么做...而不是我根据标准推出应该这样干.
而且是标准挖的坑,才导致厂商集体出问题的...
这中间和编译优化并没有太大关系,关键问题在于语义的设定.
根本的问题就在于标准,它希望通过对象本身直接调用虚成员同时具有动态的语义和静态的调用过程.为了达成这个目标,而设定了一系列的规则.却不想这些规则并没有完全达到目标,反而给实现者设下了一个圈套.导致实现的结果各异.
而要避免这一问题只要直接把这些规则删除,把这种调用统一成为动态调用即可.这对性能的影响微乎其微.
www_adintr_com 2012-08-19
  • 打赏
  • 举报
回复
在标准里,讲虚函数行为时,有指出,省略this的虚成员调用具有动态行为.而在说成员函数调用时,又指出,省略this
的成员函数调用相当于(*this).member这种形式.联系成员的动态行为,推出(*this).member等价于this->member.
于是乎,反推出(&obj)->func等价于obj.func
在现实中,也确实是这样,几乎所有的编译器都使用这种行为.
================================================================================
你这些推来推去都没有什么道理可言, 编译器优化是要根据上下文来的, 不是指根据某一条语句就能做优化的. 在某个地方的 (&a)->foo(); 被优化成了静态绑定, 并不代表所有的 (&a)->foo(); 语句都变成静态绑定了. 编译器在不改变代码行为的情况下可以做任何的优化, 甚至 (&a)->foo(); 优化来没有函数调用也没什么错, 只要优化后的代码产生的结果和不优化产生的结果一样就行.
iamnobody 2012-08-19
  • 打赏
  • 举报
回复
对well-formed的程序, C++ 允许编译器进行优化, 优化的前提是保证可见行为不变.

没发现你说的有什么坑,也没发现你说的编译器有哪个不合标准的行为.

而且, 编译器不合标准也不是C++ 的坑,你直接找编译器厂商理论去..

64,691

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧