C++对象模型:构造函数语意学

C++中隐式类型转换的问题

在C++中,可以为类定义一个类型转换运算符,允许对象被隐式地转换成另一个类型,如operator int()可以让一个类的对象当作整数使用。

但是这种隐式类型转换有时会导致意外。如为了让cin可以用于条件判断(如if (cin) ...),定义了一个operator int(),这样cin就可以被转换成一个整数值。如果不小心写错了代码,比如本该是cout << intVal;误写成了cin << intVal;,那么编译器会尝试将cin转换为整数,并且由于存在operator int(),这个转换是合法的。编译器会将这个转换后的整数值与intVal进行位移操作(即<<运算符),这显然不是程序员所期望的行为。

引入explicit关键字是为了进一步控制构造函数和转换运算符的行为。当一个构造函数被声明为explicit时,它就不能用于隐式转换;同样,explicit也可以应用于转换运算符,以防止它们被用于隐式转换。

default Constructor 的构造操作

在C++中,当一个类没有用户定义的构造函数时,编译器会在某些情况下自动提供一个默认构造函数(default constructor)。默认构造函数被称为隐式声明的默认构造函数。根据C++标准,这种隐式的默认构造函数的行为是有限制的,它不会对成员变量进行初始化,除非这些成员自身有默认构造函数,并且它们会按照自己的规则被初始化。如果只是依赖于编译器合成的默认构造函数,那么 val 和 pnext 实际上不会被显式地初始化为0。

“带有 Default Constructor”的 Member Class Object

在C++中,当一个类(例如 Bar)没有显式定义构造函数,但包含了一个或多个成员对象,并且这些成员对象有默认构造函数时,编译器会为该类合成一个默认构造函数。这个合成的默认构造函数是“非平凡”(nontrivial)的,因为它需要调用成员对象的默认构造函数来确保它们被正确初始化。

如果 Bar 类没有任何构造函数,但是它有一个成员对象 Foo foo;,而 Foo 类有默认构造函数,那么编译器会为 Bar 合成一个默认构造函数。这个默认构造函数负责调用 Foo 的默认构造函数来初始化 foo,但不会对其他成员如 char* str; 进行初始化。

如果程序员希望 str 也被初始化,他们需要提供一个显式的默认构造函数来处理这种情况。

class Dopey {
  public:
    Dopey() { /* ... */ }
};
class Sneezy {
 public:
    Sneezy(int) { /* ... */ }
    Sneezy() { /* ... */ }
};
class Bashful {
 public:
    Bashful() { /* ... */ }
};
class Snow_White {
 public:
    Dopey dopey;
    Sneezy sneezy;
    Bashful bashful;
    int mumble;
    Snow_White() : sneezy(1024) {
        mumble = 2048;
    }
};

如果 Snow_White 没有定义默认构造函数,编译器会合成一个默认构造函数,按成员声明顺序调用 Dopey、Sneezy 和 Bashful 的默认构造函数。如果程序员提供了上面那样的默认构造函数,编译器会扩展它,确保所有成员对象都被正确初始化:

// 扩展后的 Snow_White 默认构造函数
Snow_White::Snow_White() : dopey(Dopey()), sneezy(Sneezy(1024)), bashful(Bashful()) {
    mumble = 2048;
}

这里,dopey 通过其默认构造函数初始化,sneezy 通过带参数的构造函数初始化,bashful 通过其默认构造函数初始化。最后执行用户提供的初始化代码 mumble = 2048。

“带有 Default Constructor”的 Base Class

在C++中,当一个派生类(derived class)没有任何构造函数时,如果它的基类(base class)有一个默认构造函数,那么编译器会为这个派生类合成一个默认构造函数。这个合成的默认构造函数被视为“非平凡”(nontrivial),因为它需要调用基类的默认构造函数来确保基类部分被正确初始化。

情况1:派生类没有构造函数,基类有默认构造函数

class Base {
public:
    Base() { /* 基类的默认构造函数 */ }
};
class Derived : public Base {
    // 派生类没有任何构造函数
};

在这种情况下,编译器会为 Derived 类合成一个默认构造函数,将调用 Base 的默认构造函数。

inline Derived::Derived() : Base() {}

情况2:派生类提供了多个构造函数,但没有默认构造函数

如果派生类提供了多个构造函数,但其中没有一个是默认构造函数,编译器不会合成一个新的默认构造函数。相反,它会扩展现有的每一个构造函数,在每个构造函数的初始化列表中插入必要的代码,以调用基类的默认构造函数和成员对象的默认构造函数(如果有)。

class Composite {
public:
    A a;
    B b;
    C c;
    Composite() { /* 默认构造函数 */ }
};

在 Composite 类的默认构造函数中,成员对象的初始化顺序是 a、b 和 c,这与它们在类中的声明顺序一致。

“带有一个Virtual Function”的Class

虚函数表(vtable)

虚函数表(vtable)是一个静态数组,每个元素存储了指向类中虚函数的指针。每个包含虚函数的类都有一个对应的vtable。vtable是在编译时生成的,并且对于每个类来说是唯一的。

例如假设有以下类:

class Base {
public:
    virtual void func1(){/* ... */}
    virtual void func2(){/* ... */}
};

编译器会为 Base 类生成一个vtable,可能类似于:

// vtable for Base
void (Base::*vtable[2])() = {
    &Base::func1,
    &Base::func2
};

虚函数表指针(vptr)

每个包含虚函数的对象都会有一个额外的成员变量,称为虚函数表指针(vptr)。vptr是一个指向相应类的vtable的指针。vptr是在运行时动态分配的,并且每个对象都有自己的vptr。

例如,对于 Base 类的对象,其内存布局可能是这样的:

struct Base {
    void (*vptr)[];  // 指向 Base 的 vtable
    // 其他数据成员
};

在C++中当一个类包含虚函数 或 继承自一个含有虚基类的继承链时,即使该类没有显式定义任何构造函数,编译器也会为其合成一个默认构造函数。这个合成的默认构造函数是必要的,因为它需要初始化一些内部机制,如虚函数表指针(vptr)和虚基类子对象。

合成的默认构造函数可能看起来像这样:

inline Widget::Widget() {
    // 初始化 vptr
    vptr = &Widget::vtable;
}

假设我们有以下类结构:

class Widget {
public:
    virtual void flip() = 0;  // 纯虚函数
};
void flip(const Widget& widget) {
    widget.flip();
}
// 假设 Bell 和 Whistle 都派生自 Widget
class Bell : public Widget {
public:
    void flip() override { /* 实现 */ }
};
class Whistle : public Widget {
public:
    void flip() override { /* 实现 */ }
};
void foo() {
    Bell b;
    Whistle w;
    flip(b);
    flip(w);
}

在这个例子中,Widget 类有一个纯虚函数 flip(),因此 Widget 是一个抽象类。Bell 和 Whistle 类都派生自 Widget 并实现了 flip() 函数。

编译器会为每个包含虚函数的类生成一个虚函数表(vtable)。虚函数表是一个静态数组,存储了该类的所有虚函数的地址。对于 Widget 类,vtable 可能看起来像这样。

//vtable for Widget
void (Widget::*vtable[1])() = {
    &Widget::flip
};

每个包含虚函数的类的对象都会有一个额外的成员变量 vptr,它是一个指向对应类的 vtable 的指针。

在 Widget 类的对象中,vptr 会被初始化为 Widget 的 vtable 地址。

虚函数调用会被编译器重写为通过 vptr 来访问 vtable 中的函数地址。

例如,widget.flip() 会被重写为:

(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值