C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

时间:2021-07-08 23:55:58

今天做一个成绩管理系统的并发引擎,用Qt做的,仿照QtConcurrent搞了个模板基类。这里为了隐藏细节,隔离变化,把并发的东西全部包含在模板基类中。子类只需注册需要并发执行的入口函数即可在单独线程中执行。最终目标是,继承的业务逻辑类外部调用时有两个接口可选,调用syncRun同步执行;调用由引擎自动生成的asyncRun就异步执行。最终自动生成asyncRun的模板基类没能实现,主要原因是mingw对this处理的太有问题了!!原本以为编译器问题,后来才知道成员函数指针和this指针如此特殊,对此篇文章反感者请移步文章末尾直接看好文。

/*!
\author LiuBao
\date 2011/3/8
\brief 汇集了各种成员函数指针的错误用法
*/
#include <iostream>
#include <conio.h>
 
using namespace std;
 
class B;
 
class A
{
public:
    A() {cout << "A::this->" << this << endl;}
 
    void runA()
    {
        /* 错误的类型转换,拜一篇烂文所赐用上的 */
        union
        {
            void *from;             //void*类型
            void (A::*to)(int);     //A的成员函数指针类型
            void (*to2)(A*, int);   //C的函数指针类型,第一个参数是A*类型,用于传this
        }ut;
 
        ut.from = childFuncPtr;
 
        (this->*ut.to)(0);          //错误调用,用父类指针,以成员函数指针方式调用子类的成员函数
 
        /* 微软运行时库检测到运行时错误 */
        ut.to2(this, 1);            //错误调用,用C语言风格的函数指针,直接把this作为第一个参数传入调用子类的成员函数
    }
 
    template<typename FromType>
    void saveFuncPtr(FromType addr)
    {
        /* 错误的类型转换,拜一篇烂文所赐用上的 */
        union
        {
            FromType from;          //任意成员函数指针类型
            void *to;               //void*类型
        }ut;
 
        /* 把地址转换为void*保存到childFuncPtr */
        ut.from = addr;
        childFuncPtr = ut.to;
    }
 
protected:
    void *childFuncPtr;             //错误使用,由于长度可能不同,void*不可以用来保存任意成员函数地址
};
 
class B : public A
{
public:
    B()
    {
        cout << "B::this->" << this << endl;    //打印B的this指针
        this->saveFuncPtr(&B::testThis);        //保存B::testThis地址到父类的void*成员变量
    }
 
    void runB()
    {
        /* 错误的类型转换,拜一篇烂文所赐用上的 */
        union
        {
            void *from;             //void*类型
            void (B::*to)(int);     //类B的成员函数指针类型
            void (*to2)(B*, int);   //C语言的函数指针类型,第一个参数是B*类型,用于传this
        }ut;
 
        ut.from = childFuncPtr;
 
        testThis(2);                //直接调用成员函数
        (this->*ut.to)(3);          //错误调用,用函数指针调用成员函数
 
        /* 微软运行时库检测到运行时错误 */
        ut.to2(this, 4);            //错误调用,用C语言风格的函数指针,直接把this作为第一个参数传入调用子类的成员函数
    }
 
    void testThis(int i) {cout << i << " -> " << this << endl;}
};
 
int main()
{
    B b;
    b.runA();
    b.runB();
 
    _getch();
 
    return 0;
}

本例旨在测试各编译器对this的处理情况,其中有错误用法,请勿在实际项目中仿照使用!测试平台Win7x64,各编译器使用默认参数

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

gcc version 4.4.0 (GCC) 左debug右release。可见用debug版中,函数指针方法调用成员函数,成员函数中的this指针是错的!

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 16.00.30319.01 版左debug右release。debug版本有健全的运行时检查,so,release时就可以放心的给出异常值了。vs处理C语言风格调用成员函数给出运行时错误,这一点很令人赞赏!

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

这是把用C语言风格调用成员函数两处注释掉后vs编译运行结果,左debug右release。this指针完全正常。Intel(R) C++ Compiler XE for applications running on IA-32, Version 12.0.0.063 Build 20100721的运行结果与vs2010几乎完全一样,debug版本一样有运行时错误,可见是微软的运行时库在起作用。但是release版本直接崩溃,也许跟优化方式有关?微软自家编译器链自家库确实有优势,呵呵~同样,去掉两处C风格调用,this指针完全正常。

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

Embarcadero C++ 6.31左debug右release。所有调用均输出正确的this值!

总结:最费解的是mingw的结果(同学linux下用gcc测试结果一样)。父类与子类有同样的this值,同样的函数地址,父类指针直接调用子类成员函数居然可以离谱成这样!看来,奇技淫巧最终带来的后果是各种不确定,不要尝试用父类指针调用子类成员函数,更不要使用C语言的函数指针强制传递this指针!!

后续:现在才明白,我是试图用模板实现自动类型推导的委托-_-! 推荐3篇该方面的好文:

成员函数指针与高性能的C++委托(上篇)

成员函数指针与高性能的C++委托(中篇)

成员函数指针与高性能的C++委托(下篇)

http://www.cnblogs.com/codingmylife/archive/2011/03/08/1976720.html

备注:在我使用的环境(VC6)里,类的成员函数的默认调用约定是__thiscall; 关于这个调用约定,请注意在MSDN中的描述:“Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.”
注意这段话表达的意思非常重要,在X86上,this指针是通过ECX传递的,而不是通过栈传递的。

在MSDN中还有比较重要的信息是,__thiscall 在VS2005.net之前的版本中无法显示指定。
在IPF芯片和X64的机器上,__thiscall会被编译器接受但是被忽略。所以我想这是你第一个测试环境没有弹出错误对话框的原因。

http://blog.csdn.net/hifrog/archive/2004/07/03/33352.aspx
这篇里“成员函数指针实现”一节提到的:
编译器 选项 int DataPtr CodePtr Single Multi Virtual Unknown
MSVC 无 4 4 4 4 8 12 16
其中 void*属于DataPtr,静态成员函数指针也就是C语言的普通函数指针属于CodePtr,Multi是多重继承下的成员函数指针,Virtual是虚继承下的成员函数指针。

如果我把多重继承下的成员函数指针8字节长赋值给了void*(4字节长)就丢失了一部分数据。我没试过,文中这么写的。

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论的更多相关文章

  1. C&plus;&plus; 指向类成员函数指针的用法(转自*)

    类成员函数指针 类成员函数指针(member function pointer),是C++语言的一类指针数据类型,用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息. 目录 1 语法 ...

  2. 为什么NULL指针也能访问成员函数?&lpar;但不能访问成员变量&rpar;

    查看更加详细的解析请参考这篇文章:http://blog.51cto.com/9291927/2148695 看一个静态绑定的例子: 1 #include <iostream> 2 3 u ...

  3. &lbrack;转&rsqb;成员函数指针与高性能的C&plus;&plus;委托

    原文(作者:Don Clugston):Member Function Pointers and the Fastest Possible C++ Delegates 译文(作者:周翔): 成员函数指 ...

  4. set--常见成员函数及基本用法

    c++ stl集合set介绍 c++ stl集合(Set)是一种包含已排序对象的关联容器. set/multiset会根据待定的排序准则,自动将元素排序.两者不同在于前者不允许元素重复,而后者允许. ...

  5. c&sol;c&plus;&plus; 类成员变量,成员函数的存储方式,以及this指针在c&plus;&plus;中的作用

    c/c++ 类成员变量,成员函数的存储方式,以及this指针在c++中的作用 c++不会像上图那样为每一个对象的成员变量和成员函数开辟内存空间, 而是像下图那样,只为每一个对象的成员变量开辟空间.成员 ...

  6. C&plus;&plus;中 线程函数为静态函数 及 类成员函数作为回调函数

    线程函数为静态函数: 线程控制函数和是不是静态函数没关系,静态函数是在构造中分配的地址空间,只有在析构时才释放也就是全局的东西,不管线程是否运行,静态函数的地址是不变的,并不在线程堆栈中static只 ...

  7. c&sol;c&plus;&plus; 函数指针的用法

    [目录] 基本定义 c 函数指针使用举例 c++ 函数指针使用举例 函数指针作为函数参数 函数指针作为函数返回值 函数指针数组 typedef 简化函数指针操作 c语言函数指针的定义形式:返回类型 ( ...

  8. 堆(stack) 之 c 和 c&plus;&plus;模板实现(空类默认成员函数 初谈引用 内联函数)

    //stack 的基本操作 #include <iostream> using namespace std; const int maxn = 3; typedef struct Stac ...

  9. 在C&plus;&plus;的类中,普通成员函数不能作为pthread&lowbar;create的线程函数,如果要作为pthread&lowbar;create中的线程函数,必须是static

    在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static ! 在C语言中,我们使用pthread_create ...

随机推荐

  1. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  2. 验证码javaweb

    package cn.itcast.utils; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; impo ...

  3. 怎样给Eclipse添加一个Xml模板

    1.找到Window/Preferences/XML/XML Files/Editor/Templates 2.新建一个模板,设置一个名称并且在Pattern中设置自己的XML模板就可以了,同时支持导 ...

  4. java的nio之:java的nio系列教程之selector

    一:Java NIO的selector的概述===>Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件.这样,一个单独的线程 ...

  5. JavaScript高级程序设计48&period;pdf

    设备中的键盘事件 任天堂Wii等设备可以通过键码知道用户按下了哪个键 复合事件 复合事件是DOM3级事件新添加的一类事件,用于处理IME的输入序列.IME(Input Method Editor,输入 ...

  6. 5&period;如果第4题中在DOS命令下输入:java Hello 出现以下结果:Bad command or the file name 可能是什么原因?请说明理由。

    1.没有输入javac指定路径,而是直接输入java Hello. 2.前面已经用过一次指令,没有重新输入路径.

  7. Linux中的apache的服务命令

    1. 启动apachesudo service httpd start 2. 停止服务apachesudo service httpd stop 3. 重新启动apachesudo service h ...

  8. Springboot 拦截器 依赖注入失败

    解决方案2种. ====1 https://blog.csdn.net/shunhua19881987/article/details/78084679 ====2 https://www.cnblo ...

  9. &lbrack;git&rsqb; git怎样fork一个repo

    描述 我定制了一下strongswan的工程.然后想把我自己的定制变成一个repo push到远端git.tong.com与大家分享. 这个时候,应该怎么做? 如果你用过github的话.那么你可以理 ...

  10. LAB1 partIV

    PartIV 实现 处理worker 失败情况. worker 处理失败,master 应该重新分配该任务给其他的worker 处理. rpc 失败情况复杂,可能worker 结果回应丢失了,也有可能 ...