首先,将这些函数声明为内联函数,在语法上没有错误。因为inline同register一样,只是个建议,编译器并不一定真正的内联。
register关键字:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率
举个例子:
#include <iostream>
using namespace std;
class A
{
public:
inline A() {
cout << "inline construct()" <<endl;
}
inline ~A() {
cout << "inline destruct()" <<endl;
}
inline virtual void virtualFun() {
cout << "inline virtual function" <<endl;
}
};
int main()
{
A a;
a.virtualFun();
return 0;
}
//输出结果
//inline construct()
//inline virtual function
//inline destruct()
构造函数和析构函数声明为内联函数是没有意义的
《Effective C++》中所阐述的是:将构造函数和析构函数声明为inline是没有什么意义的,即编译器并不真正对声明为inline的构造和析构函数进行内联操作,因为编译器会在构造和析构函数中添加额外的操作(申请/释放内存,构造/析构对象等),致使构造函数/析构函数并不像看上去的那么精简。其次,class中的函数默认是inline型的,编译器也只是有选择性的inline,将构造函数和析构函数声明为内联函数是没有什么意义的。
将虚函数声明为inline,要分情况讨论
有的人认为虚函数被声明为inline,但是编译器并没有对其内联,他们给出的理由是inline是编译期决定的,而虚函数是运行期决定的,即在不知道将要调用哪个函数的情况下,如何将函数内联呢?
上述观点看似正确,其实不然,如果虚函数在编译器就能够决定将要调用哪个函数时,就能够内联,那么什么情况下编译器可以确定要调用哪个函数呢,答案是当用对象调用虚函数(此时不具有多态性)时,就内联展开
综上,当是指向派生类的指针(多态性)调用声明为inline的虚函数时,不会内联展开;当是对象本身调用虚函数时,会内联展开,当然前提依然是函数并不复杂的情况下。
虚函数不是在运行时才确定的吗?然而inline是在编译器确定的?那么当使用对象本身却可以展开内联函数。对比父类指针调用虚函数却不会内联展开?这是为什么呢?给一个示例
#include <iostream>
class Base {
public:
virtual void show() {
std::cout << "Base::show()" << std::endl;
}
};
class Derived : public Base {
public:
virtual void show() {
std::cout << "Derived::show()" << std::endl;
}
};
int main() {
Base *ptr = new Derived(); // 指向派生类对象的基类指针
ptr->show(); // 通过基类指针调用虚函数,不会内联展开
delete ptr;
return 0;
}
在这个示例中,Base 类有一个虚函数 show(),Derived 类继承自 Base 并重写了 show() 函数。在 main() 函数中,我们创建了一个指向 Derived 对象的 Base 类指针,并通过这个指针调用了 show() 函数。由于编译器无法在编译时确定指针 ptr 指向的实际对象类型,因此无法进行静态绑定,也就无法进行内联展开。
相比之下,如果我们使用对象本身来调用虚函数,编译器在编译时就可以确定对象的类型,从而可以进行静态绑定和内联展开。
所以并不是所有虚函数都是在运行时确定的,不具有多态的虚函数在编译期也可以确定?
你提出了一个很重要的观点,是的,不是所有的虚函数都是在运行时确定的。在某些情况下,编译器可以在编译时静态地确定虚函数的调用目标,这被称为静态绑定(static binding)。在这种情况下,即使函数被声明为虚函数,编译器也可以在编译期间确定调用哪个函数,而不需要等到运行时。
静态绑定发生在以下情况下:
- 当对象的类型在编译期间是已知的,且该类型不是指针或引用。
- 当使用对象本身而不是指针或引用调用虚函数时。
因此,在这些情况下,编译器可以在编译期间确定虚函数的调用目标,从而可以进行静态绑定和优化,包括内联展开。
而在其他情况下,例如当使用指针或引用调用虚函数时,编译器无法在编译期间确定实际对象的类型,因此需要等到运行时才能确定虚函数的调用目标,这被称为动态绑定(dynamic binding)。
综上所述,确实并不是所有的虚函数都是在运行时确定的,静态绑定的虚函数在编译期可以确定调用目标,而动态绑定的虚函数需要等到运行时确定。
动态绑定(dynamic binding)是指在运行时根据对象的实际类型来确定调用的函数,这是多态性的一种体现。在使用指针或引用调用虚函数时,如果对象的类型在运行时才能确定,编译器就无法在编译期确定调用的函数,而需要等到运行时根据对象的实际类型来确定。这种情况下,编译器无法进行静态绑定,因此需要进行动态绑定。
动态绑定发生在以下情况下:
- 当使用指向派生类对象的基类指针或引用调用虚函数时。
- 当基类引用或指针指向不同派生类对象,且调用相同的虚函数。
在这些情况下,编译器无法在编译期确定调用的函数,因为它不知道指针或引用实际指向的对象的类型。因此,需要等到运行时根据对象的实际类型来确定调用的虚函数,这就是动态绑定的概念。
总之,动态绑定允许程序在运行时根据对象的实际类型来确定调用的虚函数,这样就能够实现多态性,即使对象的类型在编译期是未知的。
为什么无法在编译期确定呢?
在使用指针或引用调用虚函数时,编译器无法在编译期确定调用的函数,是因为编译器只能根据指针或引用的静态类型来确定调用的函数,而无法确定其指向的对象的实际类型。
考虑以下情况:
Base* ptr = new Derived();
ptr->show();
在这段代码中,ptr
是一个指向 Base
类对象的指针,但在运行时却指向了一个 Derived
类对象。当调用 ptr->show()
时,编译器只知道 ptr
是一个 Base
类指针,因此只能根据 Base
类中 show()
函数的定义来确定调用的函数。然而,由于 show()
是一个虚函数,并且 ptr
指向的是一个 Derived
类对象,因此实际上应该调用 Derived
类中的 show()
函数。
由于编译器无法在编译期确定 ptr
所指向的对象的实际类型,因此也就无法确定调用的函数。这种情况下,编译器只能生成代码来在运行时动态地确定调用的函数,这就是动态绑定的原理。
因此,编译器无法在编译期确定调用的虚函数,需要等到运行时根据对象的实际类型来确定。
也就是new在编译期不执行?
是的,new
运算符用于在运行时动态分配内存并返回指向该内存的指针。因此,new
运算符的执行发生在程序运行时而不是编译时。在使用 new
创建对象时,对象的类型是在运行时确定的,因此编译器无法在编译期确定对象的实际类型,这也是为什么无法在编译期确定虚函数的调用目标的原因之一。