C/C++关于普通函数,成员函数,静态成员函数,函数指针的理解

最近在学习C++11的多线程技术,对std::thread构造函数需要传入的第一个参数的类型产生的疑惑,在C++11中增加了可调用对象(Callable Objects)的概念,包括以下几种:

  • 函数指针
  • 重载operator()运算符的类对象
  • lambda表达式
  • std::function

std::thread的第一个参数要求的类型就是Callable Objects,让我产生疑惑就是第一种——函数指针,因为在一些书籍里我看到了很多不同的写法。比如:

// 第一种
void hello()
{
    printf("thread hello!!!\n");
}
std::thread t1(hello);

// 第二种
class thread_test
{
public:
    thread_test() = default;

    void run()
    {
        printf("thread_test running !!!\n");
    }
};
thread_test obj_test;
std::thread t2(&thread_test::run, &obj_test);

那普通函数、成员函数、静态成员函数以及在函数名前加"&"到底有什么区别呢?

(以下仅是些个人理解,我不是汇编大神,不能从更本质的角度分析,如有错误请多包涵)

首先是说普通函数、成员函数、静态成员函数三者的区别:

从内存本质上来说,三者其实没太大区别,都位于代码段。成员函数只是比普通函数多了一层类的外壳,调用的时候需要与实际的类关联。普通函数更像是静态成员函数,两者传入参数的方式比较一致,而非静态成员函数的区别就是的第一个参数是默认的this指针(当然不需要显式的调用)。

无论是静态或者是非静态成员函数,本质上都是属于类的,而不是具体对象。从设计模式的角度其实很好理解,因为这些成员函数对于每个实际对象其实都是一样的,而且是在class设计实现阶段就定死不会变化的,所以只需要在内存代码段保留一份即可。而类中对于每个对象可能不同的部分,比如成员变量、虚函数表等,这些可能在程序执行过程中发生动态变化,所以需要和具体对象绑定在一起,有多少个对象,就有多少个成员变量、虚函数表。

总结如下:

成员函数是和类绑定在一起的,有且仅有一份,位于代码段。

成员变量、虚函数表等不同对象可能不同值的部分,和具体所属的对象绑定在一起,位于栈上或堆上(new)。

 

再者聊聊普通函数、成员函数、静态成员函数、函数名前加"&"以及函数指针的关系

函数名可以直接赋值给函数指针,这类似于数组名可以直接赋值给指针的用法。函数名和数组名从本质上来说都不是指针类型,执行赋值操作实际是编译器帮我们做了隐性的转换,它会默认帮我们在函数名或者数组名前加上取地址符"&"。比如上面的例子,函数名hello和&hello从数值上来说是一样的,其实这是很匪夷所思的,函数指针不应该函数hello存放的地址吗,怎么加不加"&"一样呢?从个人角度来说,加上取地址符更符合我的理解。很多时候,越简练的程序理解起来就越困难,这就是代价吧。

成员函数和静态成员函数也类似,给函数指针赋值或者当做函数指针参数传入时,函数名前加不加"&"都可以。用一个实际的程序例证下。

#include <iostream>

void hello()
{
    printf("thread hello!!!\n");
}

class test
{
public:
    test() = default;

    void run()
    {
        printf("test running !!!\n");
    }

    static void static_run()
    {
        printf("test static running !!!\n");
    }
};

int main()
{
    printf("hello = 0x%x\n", hello);
    printf("&hello = 0x%x\n\n", &hello);

    printf("test::run = 0x%x\n", test::run);
    printf("&test::run = 0x%x\n\n", &test::run);

    printf("test::static_run = 0x%x\n", test::static_run);
    printf("&test::static_run = 0x%x\n\n", &test::static_run);

    getchar();
    return 0;
}

实际运行结果:

hello = 0x401080
&hello = 0x401080

test::run = 0xffffcba0
&test::run = 0xffffcba0

test::static_run = 0x401840
&test::static_run = 0x401840

可以看到,普通函数、静态成员函数、非静态成员函数,函数名与函数名前加"&"的值本质上是一样的。

创建thread线程时,函数名前需要加取地址符吗?

答案是加不加都可以。理由同上,C++编译器会自动为我们添加取地址符,特殊的是非静态成员函数至少需要一个参数(this指针),所以创建线程时必须得有一个实际的对象为我们提供这个this地址,否则编译是通不过的。Talk is cheap, show me the code 上代码验证。

#include <iostream>
#include <thread>

void hello()
{
    printf("thread hello!!!\n");
}

class thread_test
{
public:
    thread_test() = default;

    void run()
    {
        printf("class thread_test running !!!\n");
    }

    static void static_run()
    {
        printf("class thread_test static running !!!\n");
    }
};

int main()
{

    thread_test obj_test;

    std::thread t1(hello);
    std::thread t2(&hello);
    t1.join();
    t2.join();
    std::cout << std::endl;

    std::thread t3(thread_test::static_run);
    std::thread t4(&thread_test::static_run);
    t3.join();
    t4.join();
    std::cout << std::endl;
    
    std::thread t5(thread_test::run, &obj_test);
    std::thread t6(&thread_test::run, &obj_test);
    t5.join();
    t6.join();

    getchar();
    return 0;
}

实际运行结果:

thread hello!!!
thread hello!!!

class thread_test static running !!!
class thread_test static running !!!

class thread_test running !!!
class thread_test running !!!

可以看到普通函数、静态成员函数、非静态成员函数三者函数名前,无论加不加"&"都可以作为std::thread的函数指针参数正常运行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章