这篇博客开始总结《深度探索C++对象模型》的第四章,即C++中成员函数(除了构造函数和析构函数)相关的内容。介绍了C++类中的成员函数,包括常规成员函数、虚函数、静态函数。
其实我一直犹豫要不要写这一篇,因为在书中讲到多重继承下的虚函数时只是说实现很复杂,并没有具体讲有什么样的实现,我个人也因为技术力的原因没办法总结出g++实现的多重继承下的虚函数实现,只能通过调试软件管中窥豹。但我最终还是决定,权当给自己巩固的机会。

C++类中各种成员函数

非静态(非static)、非虚(非virtual)成员函数

这是最常规的一种成员函数,必须通过其绑定的对象或指针调用。

// test20.cpp
#include <cstdio>
class Test20 {
public:
    explicit
    Test20(int i)
        : m_i(i)
    {}
    int getInt() const { return m_i; }
    void setInt(int i) { m_i = i; }
private:
    int m_i;
};
int main() {
    Test20 t20(1);
    std::printf("%d\n", t20.getInt());
    t20.setInt(2);
    std::printf("%d\n", t20.getInt());
    Test20* pt20 = &t20;
    pt20->setInt(3);
    std::printf("%d\n", t20.getInt());
}
// Output:
//  1
//  2
//  3

实际上,编译器会将这种成员函数改写为非成员函数,并在函数的参数列表前面加一个类型为Test20* const的形式参数(如果成员函数加了const关键词则形参类型为const Test20* const),参数名为this。而且,在成员函数内所有对于该类所有的成员函数、成员变量的操作都通过this指针来进行。以Test20::setInt为例,改写之后就变成了:

void setInt(Test20* const this, int i) {
    this->m_i = i;
}

同时,为了放置名字冲突,编译器还会对函数的名字进行修饰,包括在前面添加其命名空间信息,其后添加参数信息等,这里就不详细展开了。

虚函数

为了支持多态,C++引入了虚函数这一概念。对于虚函数的调用会转化为对于虚函数表中某一表项内填写的函数指针的调用。
如下面的调用:

ptr->virtalFunc();

实际上会转化为:

// C++伪码
(ptr->vptr[1])(ptr);

其中ptr为指向某一对象的指针,virtualFunc为对象的某个virtual的成员函数,vptr为其虚函数表,1virtualFunc这个虚函数在虚函数表中的下标。
注意,只有使用指针和引用调用虚函数时才会表现出多态性(也就是从虚函数表中取函数的实际地址然后调用),而使用对象调用一个虚函数,即便其被声明为一个虚函数,也只是被当作常规函数调用,不会展现多态性(也就是不会从虚函数表中取得实际函数地址)。

// test21.cpp
#include <cstdio>
class Test21 {
public:
    explicit
    Test21(int i)
        : m_i(i)
    {}
    virtual
    int getInt() { return m_i; }
private:
    int m_i;
};
void call(Test21& t21) {
    int i = t21.getInt();
}
int main() {
    Test21 t21(1);
    int i = t21.getInt();
    call(t21);
    Test21* pt21 = &t21;
    i = pt21->getInt();
}

C++对象模型:g++的实现(四)
C++对象模型:g++的实现(四)
可以看到在main函数中使用t21这个对象调用虚函数getInt()直接call了函数真实地址,没有多态性;而使用pt21这个指针调用虚函数则从虚表中取出了虚函数的地址,然后call这个地址,会有多态性。而在call函数中,使用引用t21也时从虚表中取得的虚函数地址。
关于继承体系下的虚函数会在下个博客讲。

静态成员函数

对于静态成员函数,编译器不会改变其形参列表,也就是说不会传入一个名为this的、指向类的一个实例的指针。所以静态成员函数一般通过类名直接调用,或是使用类的实例对象/指针/引用调用,但和使用类名直接调用不会有区别,也不会传入this指针。
而且因为静态函数不传入this指针,所以静态函数内不能直接读写非静态成员变量和非静态成员函数(因为不知道其绑定的对象)。同时,静态函数也不能定义为const,因为其他成员函数定义为const时为了限定this指针,而静态成员函数不传入this指针,定义为const也就没有意义。

// test22.cpp
#include <cstdio>
class Test22 {
public:
    Test22(int i)
        : m_i(i)
    {}
    static
    void staticMethod() {
        std::printf("static member %d\n", s_i);     // OK
        // std::printf("commom member %d\n", m_i);  // ERROR
        anotherStaticMethod();                      // OK
        // commomMethod();                          // ERROR
    }
    static
    void anotherStaticMethod() {
        std::printf("Test::anotherStaticMethod()\n");
    }
    void commomMethod() {
        std::printf("Test22::commomMethod()\n");
    }
private:
    int m_i;
    static const int s_i;
};
const int Test22::s_i = 2;
int main() {
    Test22 t22(1);
    t22.staticMethod();     //OK
    Test22::staticMethod(); // OK
}
// Output
//  static member 2
//  Test::anotherStaticMethod()
//  static member 2
//  Test::anotherStaticMethod()

这篇博客就先总结到这里,关于虚函数的详细实现和成员函数指针等内容就放到后续的博客中吧。

发表回复