程序执行所用的时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 测试函数test优化前后执行所用的时间的一种方法 void test() { // do something } int main() { clock_t start, end; // clock返回的是毫秒数 start = clock(); std::cout << start << std::endl; for (size_t i = 0; i < 1000000; i++) { test(); } end = clock(); std::cout << end << std::endl; return 0; } |
关于优化的一点
标准
- C++标准允许一种(编译器)实现省略创建一个只是为了初始化另一个同类型对象的临时对象。指定这个参数(-fno-elide-constructors)将关闭这种优化,强制G++在所有情况下调用拷贝构造函数。
表现
- 当编译器遇到需要用一个类对象作为另一个类对象初值的情况时,编译器可能会有优化的动作,而且可能不同是编译器实现表现会有所不同。
开关
下面这个参数可以关掉g++的优化:
1 |
g++ -fno_elide_constructors aet.cpp -o aet |
必须使用成员列表初始化的情况
- 如果类A有个成员是个引用,必须在初始化列表中初始化这个成员。
1 2 3 4 5 6 7 |
class A { public: explicit A(int a) : a_(a) { } int& a_; }; |
- 如果类A有个const成员,需要在初始化列表中初始化这个成员。
1 2 3 4 5 6 7 |
class A { public: explicit A(int a) : a_(a) { } const int a_; }; |
- 如果类B继承了类A,但是类A的构造是带有参数的构造,那么类B需要在自己的构造函数的初始化列表中调用基类A的构造并传过去需要的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class A { public: explicit A(int lhs, int rhs) : lhs_(lhs), rhs_(rhs) { } private: int lhs_; int rhs_; }; class B : public A { public: explicit B(int a, int b) : a_(a), b_(b), A(a, b) { } private: int a_; int b_; }; |
- 如果类A的成员变量b是B类类型,而B类的构造函数带有参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class B { public: explicit B(int b) { } }; class A { public: explicit A(int a) : a_(a), b_(a) { } private: int a_; B b_; }; |
初始化列表的效率问题
- 类A里面含有一个B类类型的对象成员,如果没有对这个对象成员做初始化工作,那么当执行到类A的构造函数的时候,编译器会创建出一个临时的B类类型的对象,并用这个临时对象去初始化类A里面的B类类型的对象成员。
- 如果在类A的构造函数里面,做了对这个B类类型对象成员的初始化工作,那么实质上是在类A的构造函数里面,在编译器安插的其他代码后面,可能是创建了一个临时对象,并调用了成员对象的拷贝赋值运算符使用这个临时对象去初始化了这个成员对象。
- 这样的做法效率相对较低。
- 可以直接把成员对象的初始化工作放到初始化列表里面,这样会相对高效一些。
- 因为放在构造函数初始化里面里面的内容,编译器会把这些内容最后插入到函数体内,并且执行的时机要比函数内的我们写的其他代码要更早。
- 还有就是,变量声明的顺序是它们在初始化列表里面被初始化的顺序有关的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// b_先声明,所以先初始化b_,执行b_(a_) // 然而,这时候a_还没有被初始化,所以b_的值会是一个随机的。 class A { public: explicit A(int a) : a_(a), b_(a_) {} void show() { std::cout << a_ << " " << b_ << std::endl; } int b_; int a_; }; int main() { A a(10); a.show(); return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 现在是a_先声明,所以先被初始化 // 再初始化b_的时候,a_的值已经是期望的了,所以b_的值也是期望的 class A { public: explicit A(int a) : a_(a), b_(a_) {} void show() { std::cout << a_ << " " << b_ << std::endl; } int a_; int b_; }; int main() { A a(10); a.show(); return 0; } |
虚函数表指针的位置
- 虚函数表的指针(vptr)位于对象内存的开头(低地址)的4个字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class A { public: int i_; explicit A(int i) : i_(i) {} virtual void show() { std::cout << "i_: aet" << std::endl; } }; int main() { A a(10); std::cout << "取虚函数表的地址" << std::endl; long* pvptr = (long*)(&a); long* vtptr = (long*)(*pvptr); typedef void(*func)(void); func f = (func)vtptr[0]; f(); return 0; } |
虚函数表
- 一个类有虚函数才会有虚函数表。
- 同属于一个类的所有对象共享一张虚函数表,但是不同的对象有各自的虚函数表指针(也就是用来存虚函数表地址的vptr是不同的)。
- 基类有虚函数表,派生类也肯定会有。
- 一些方法在基类中是虚函数,在派生类中即便不写virtual,它也是虚函数。
- 不管父类还是子类,都只有一个虚函数表。
- 如果派生类中完全没有新的虚函数,则可以认为派生类的虚函数表和基类的虚函数表在内容上是相同的。
- 虽然内容可以认为是相同的,但是它们实质上处于内存中不同的位置,是两张表。
- 虚函数表中的每一项,都保存着一个虚函数的首地址。
- 如果派生类的虚函数表中的某项和基类的虚函数表中的某项是同一个函数,意味着派生类没有覆盖基类的该虚函数,它们存的虚函数的地址是相同的。
多重继承下的虚函数表
- 一个派生类继承了多个基类,则它有多个虚函数表指针。
- 同时,派生类有多个虚函数表。
- 而且,在这种多继承的情况下,各个基类的vptr按照继承顺序几次放在派生类的内存空间中。
并且,派生类与第一个基类公用同一个vptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class A { public: virtual void a() { std::cout << "A::a" << std::endl; } virtual void b() { std::cout << "A::a" << std::endl; } }; class A2 { public: virtual void c() { std::cout << "A2::c" << std::endl; } virtual void d() { std::cout << "A2::d" << std::endl; } }; class B : public A, public A2 { public: void a() override { std::cout << "B::a" << std::endl; } void d() override { std::cout << "B::d" << std::endl; } virtual void e(int i) { std::cout << "B::e" << std::endl; } }; int main() { // ... } |
查看虚函数表的辅助方法
VS
- 在Developer Command Prompt for VS 2019里面,利用cl.exe进行查看。
1 |
cl /d1 reportSingleClassLayoutB "F:\MyCode Repo\C++ Repo\Test\object_model\3_Inherit_vtable\3_Inherit_vtable.cpp" |
g++
- 利用一些g++的参数
1 |
g++ -fdump-class-hierarchy -fsyntax-only aet.cpp |
虚函数表指针的创建时机
- 对象什么时候创建出来的,vptr就是什么时候创建出来的。
在运行时确定的相关信息。
虚函数表的创建时机
- 在编译器编译期间就为每个类确定好了对应的vtbl的内容。
虚函数在虚拟存储器中的位置
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ C++标准库_cfenv02/14
- ♥ C++_函数模板、类模板、特化、模板元编程、SFINAE、概念06/22
- ♥ 51CTO:Linux C++网络编程四08/19
- ♥ Soui应用 动画一06/24
- ♥ WTL 概述03/10
- ♥ Soui八06/20