Virtual Functions and Inheritance
This section presents the C++ code for a typical virtual function invocation scenario. This is then compared to the equivalent C code.
1 // A typical example of inheritance and virtual function use.
2
3 // We would be mapping this code to equivalent C.
4
5 // Prototype graphics library function to draw a circle
6 #include <iostream>
7 #include <ctime>
8 void glib_draw_circle (int x, int y, int radius);
9
10 // Shape base class declaration
11
12 class Shape
13
14 {
15
16 protected:
17
18 int m_x; // X coordinate
19
20 int m_y; // Y coordinate
21
22
23
24 public:
25
26 // Pure virtual function for drawing
27
28 virtual void Draw() = 0;
29
30
31
32 // A regular virtual function
33
34 virtual void MoveTo(int newX, int newY);
35
36
37
38 // Regular method, not overridable.
39
40 void Erase();
41
42
43
44 // Constructor for Shape
45
46 Shape(int x, int y);
47
48
49
50 // Virtual destructor for Shape
51
52 virtual ~Shape();
53
54 };
55
56
57
58 // Circle class declaration
59
60 class Circle : public Shape
61
62 {
63
64 private:
65
66 int m_radius; // Radius of the circle
67
68
69
70 public:
71
72 // Override to draw a circle
73
74 virtual void Draw();
75
76
77
78 // Constructor for Circle
79
80 Circle(int x, int y, int radius);
81
82
83
84 // Destructor for Circle
85
86 virtual ~Circle();
87
88 };
89
90
91
92 // Shape constructor implementation
93
94 Shape::Shape(int x, int y)
95
96 {
97
98 m_x = x;
99
100 m_y = y;
101
102 }
103
104
105
106 // Shape destructor implementation
107
108 Shape::~Shape()
109
110 {
111
112 //...
113
114 }
115
116 void Shape::MoveTo(int newX, int newY)
117 {
118 m_x=newX;
119 m_y=newY;
120 std::cout<<"MoveTo X:"<<m_x<<" Y:"<<m_y<<std::endl;
121 }
122
123 void Shape::Erase()
124 {
125 m_x=0;
126 m_y=0;
127 std::cout<<"Erase"<<std::endl;
128 }
129 // Circle constructor implementation
130
131 Circle::Circle(int x, int y, int radius) : Shape (x, y)
132
133 {
134
135 m_radius = radius;
136
137 }
138
139
140
141 // Circle destructor implementation
142
143 Circle::~Circle()
144
145 {
146
147 //...
148
149 }
150
151
152
153 // Circle override of the pure virtual Draw method.
154
155 void Circle::Draw()
156
157 {
158
159 glib_draw_circle(m_x, m_y, m_radius);
160
161 }
162
163
164
165 void glib_draw_circle (int x, int y, int radius)
166 {
167 std::cout <<"draw circle"<<" x:"<<x<<" y:"<<y<<" radius:"<<radius<<std::endl;
168 }
169 int main()
170
171 {
172 clock_t start,finish;
173
174 start = clock();
175
176 // Define a circle with a center at (50,100) and a radius of 25
177
178 Shape *pShape = new Circle(50, 100, 25);
179
180
181
182 // Define a circle with a center at (5,5) and a radius of 2
183
184 Circle aCircle(5,5, 2);
185
186
187
188 // Various operations on a Circle via a Shape pointer
189
190 pShape->Draw();
191
192 pShape->MoveTo(100, 100);
193
194 pShape->Erase();
195
196 delete pShape;
197
198
199
200 // Invoking the Draw method directly
201
202 aCircle.Draw();
203 finish = clock();
204
205 std::cout <<"execute time:" <<( (double)(finish - start)*1000000/CLOCKS_PER_SEC )<<"us"<<std::endl;
206 return 0;
207
208 }
C code implementing the above C++ functionality is shown below. The code also includes compiler generated constructs like vtables. Virtual function access using vtables is also covered. (The presentation here has been simplified here to aid understanding).
1 /*
2
3 The following code maps the C++ code for the Shape and Circle classes
4
5 to C code.
6
7 */
8 #include <stdio.h>
9
10 #include <stdlib.h>
11 #include <time.h>
12 #define TRUE 1
13
14 #define FALSE 0
15
16 typedef int BOOLEAN;
17
18 /*
19
20 Error handler used to stuff dummy VTable
21
22 entries. This is covered later.
23
24 */
25
26 void pure_virtual_called_error_handler();
27 /* Prototype graphics library function to draw a circle */
28 void glib_draw_circle (int x, int y, int radius);
29 typedef void (*VirtualFunctionPointer)();
30 /*
31
32 VTable structure used by the compiler to keep
33
34 track of the virtual functions associated with a class.
35
36 There is one instance of a VTable for every class
37
38 containing virtual functions. All instances of
39
40 a given class point to the same VTable.
41
42 */
43
44 typedef struct VTable
45 { /*
46 d and i fields are used when multiple inheritance and virtual
47 base classes are involved. We will be ignoring them for this
48 discussion.
49
50 */
51
52 int d;
53 int i;
54 /*
55
56 A function pointer to the virtual function to be called is stored here.
57
58 */
59 VirtualFunctionPointer pFunc;
60 }VTable;
61
62
63
64 /*
65
66 The Shape class maps into the Shape structure in C. All
67
68 the member variables present in the class are included
69
70 as structure elements. Since Shape contains a virtual
71
72 function, a pointer to the VTable has also been added.
73
74 */
75
76 typedef struct Shape
77 {
78
79 int m_x;
80 int m_y;
81 /*
82
83 The C++ compiler inserts an extra pointer to a vtable which
84
85 will keep a function pointer to the virtual function that
86
87 should be called.
88
89 */
90 VTable *pVTable;
91 }Shape;
92
93
94
95 /*
96
97 Function prototypes that correspond to the C++ methods
98
99 for the Shape class,
100
101 */
102
103 Shape *Shape_Constructor(Shape *this_ptr, int x, int y);
104
105 void Shape_Destructor(Shape *this_ptr, BOOLEAN dynamic);
106
107 void Shape_MoveTo(Shape *this_ptr, int newX, int newY);
108
109 void Shape_Erase(Shape *this_ptr);
110
111
112
113 /*
114
115 The Shape vtable array contains entries for Draw and MoveTo
116
117 virtual functions. Notice that there is no entry for Erase,
118
119 as it is not virtual. Also, the first two fields for every
120
121 vtable entry are zero, these fields might have non zero
122
123 values with multiple inheritance, virtual base classes
124
125 A third entry has also been defined for the virtual destructor
126
127 */
128 VTable VTableArrayForShape[] =
129 { /*
130
131 Vtable entry virtual function Draw.
132
133 Since Draw is pure virtual, this entry
134
135 should never be invoked, so call error handler
136
137 */
138
139 { 0, 0, (VirtualFunctionPointer) pure_virtual_called_error_handler},
140 /*
141
142 This vtable entry invokes the base class's
143 MoveTo method.
144 */
145 { 0, 0, (VirtualFunctionPointer)Shape_MoveTo },
146 /* Entry for the virtual destructor */
147 { 0, 0, (VirtualFunctionPointer)Shape_Destructor }
148
149 };
150
151 /*
152
153 The struct Circle maps to the Circle class in the C++ code.
154
155 The layout of the structure is:
156
157 - Member variables inherited from the the base class Shape.
158
159 - Vtable pointer for the class.
160
161 - Member variables added by the inheriting class Circle.
162
163 */
164
165 typedef struct Circle
166 {
167 /* Fields inherited from Shape */
168 int m_x;
169 int m_y;
170 VTable *pVTable;
171 /* Fields added by Circle */
172 int m_radius;
173 }Circle;
174
175 /*
176 Function prototypes for methods in the Circle class.
177 */
178 Circle *Circle_Constructor(Circle *this_ptr, int x, int y, int radius);
179 void Circle_Draw(Circle *this_ptr);
180 void Circle_Destructor(Circle *this_ptr, BOOLEAN dynamic);
181 /* Vtable array for Circle */
182 VTable VTableArrayForCircle[] =
183 { /*
184 Vtable entry virtual function Draw.
185 Circle_Draw method will be invoked when Shape's
186 Draw method is invoked
187 */
188 { 0, 0, (VirtualFunctionPointer) Circle_Draw },
189 /*
190
191 This vtable entry invokes the base class's
192
193 MoveTo method.
194
195 */
196 { 0, 0, (VirtualFunctionPointer) Shape_MoveTo },
197
198 /* Entry for the virtual destructor */
199
200 { 0, 0, (VirtualFunctionPointer) Circle_Destructor }
201 };
202
203 Shape *Shape_Constructor(Shape *this_ptr, int x, int y)
204 {
205 /* Check if memory has been allocated for struct Shape. */
206 if (this_ptr == NULL)
207 {
208 /* Allocate memory of size Shape. */
209 this_ptr = (Shape *) malloc(sizeof(Shape));
210 }
211
212 /*
213 Once the memory has been allocated for Shape,
214 initialise members of Shape.
215 */
216 if (this_ptr)
217 { /* Initialize the VTable pointer to point to shape */
218 this_ptr->pVTable = VTableArrayForShape;
219 this_ptr->m_x = x;
220 this_ptr->m_y = y;
221 }
222 return this_ptr;
223 }
224
225
226
227 void Shape_Destructor(Shape *this_ptr, BOOLEAN dynamic)
228 { /*
229
230 Restore the VTable to that for Shape. This is
231
232 required so that the destructor does not invoke
233
234 a virtual function defined by a inheriting class.
235
236 (The base class destructor is invoked after inheriting
237
238 class actions have been completed. Thus it is not
239
240 safe to invoke the ineriting class methods from the
241
242 base class destructor)
243
244 */
245 this_ptr->pVTable = VTableArrayForShape;
246
247 /*
248
249 If the memory was dynamically allocated
250
251 for Shape, explicitly free it.
252
253 */
254 if (dynamic)
255 {
256 free(this_ptr);
257 }
258 }
259
260 void Shape_MoveTo(Shape *this_ptr, int newX, int newY)
261 {
262 this_ptr->m_x = newX;
263 this_ptr->m_y = newY;
264 printf("MoveTo X:%d Y:%d \n",newX,newY);
265 }
266
267 void Shape_Erase(Shape *this_ptr)
268 {
269 this_ptr->m_x = 0;
270 this_ptr->m_y = 0;
271 printf("Erase\n");
272 }
273
274 Circle *Circle_Constructor(Circle *this_ptr, int x, int y, int radius)
275
276 {
277
278 /* Check if memory has been allocated for struct Circle. */
279
280 if (this_ptr == NULL)
281
282 {
283 /* Allocate memory of size Circle. */
284 this_ptr = (Circle *) malloc(sizeof(Circle));
285 }
286
287
288
289 /*
290
291 Once the memory has been allocated for Circle,
292
293 initialise members of Circle.
294
295 */
296
297 if (this_ptr)
298 { /* Invoking the base class constructor */
299 Shape_Constructor((Shape *)this_ptr, x, y);
300 this_ptr->pVTable = VTableArrayForCircle;
301 this_ptr->m_radius = radius;
302 }
303 return this_ptr;
304 }
305
306
307
308 void Circle_Destructor(Circle *this_ptr, BOOLEAN dynamic)
309 {
310 /* Restore the VTable to that for Circle */
311 this_ptr->pVTable = VTableArrayForCircle;
312 /*
313
314 Invoke the base class destructor after ineriting class
315
316 destructor actions have been completed. Also note that
317
318 that the dynamic flag is set to false so that the shape
319
320 destructor does not free any memory.
321
322 */
323
324 Shape_Destructor((Shape *) this_ptr, FALSE);
325 /*
326 If the memory was dynamically allocated
327 for Circle, explicitly free it.
328
329 */
330 if (dynamic)
331 {
332 free(this_ptr);
333 }
334 }
335
336
337
338 void Circle_Draw(Circle *this_ptr)
339 {
340 glib_draw_circle(this_ptr->m_x, this_ptr->m_y, this_ptr->m_radius);
341 }
342
343 void pure_virtual_called_error_handler()
344 {
345 }
346 void glib_draw_circle (int x, int y, int radius)
347 {
348 printf("draw circle x:%d y:%d radius:%d \n",x,y,radius);
349 }
350
351 void main()
352 {
353 clock_t start,finish;
354 /*
355
356 Dynamically allocate memory by passing NULL in this arguement.
357
358 Also initialse members of struct pointed to by pShape.
359
360 */
361
362 Shape *pShape = NULL;
363
364 /* Define a local variable aCircle of type struct Circle. */
365
366 Circle aCircle;
367
368 start=clock();
369
370 pShape = (Shape *) Circle_Constructor(NULL, 50, 100, 25);
371 /* Initialise members of struct variable aCircle. */
372
373 Circle_Constructor(&aCircle, 5, 5, 2);
374 /*
375
376 Virtual function Draw is called for the shape pointer. The compiler
377
378 has allocated 0 offset array entry to the Draw virtual function.
379
380 This code corresponds to "pShape->Draw();"
381
382 */
383 (pShape->pVTable[0].pFunc)(pShape);
384
385 /*
386
387 Virtual function MoveTo is called for the shape pointer. The compiler
388
389 has allocared 1 offset array entry to the MoveTo virtual function.
390
391 This code corresponds to "pShape->MoveTo(100, 100);"
392
393 */
394
395 (pShape->pVTable[1].pFunc)(pShape, 100, 100);
396
397 /*
398
399 The following code represents the Erase method. This method is
400
401 not virtual and it is only defined in the base class. Thus
402
403 the Shape_Erase C function is called.
404
405 */
406
407 Shape_Erase(pShape);
408
409 /* Delete memory pointed to by pShape (explicit delete in original code).
410
411 Since the destructor is declared virtual, the compiler has allocated
412
413 2 offset entry to the virtual destructor
414
415 This code corresponds to "delete pShape;".
416
417 */
418
419 (pShape->pVTable[2].pFunc)(pShape, TRUE);
420
421 /*
422
423 The following code corresponds to aCircle.Draw().
424
425 Here the compiler can invoke the method directly instead of
426
427 going through the vtable, since the type of aCircle is fully
428
429 known. (This is very much compiler dependent. Dumb compilers will
430
431 still invoke the method through the vtable).
432
433 */
434
435 Circle_Draw(&aCircle);
436
437
438 /*
439
440 Since memory was allocated from the stack for local struct
441
442 variable aCircle, it will be deallocated when aCircle goes out of scope.
443
444 The destructor will also be invoked. Notice that dynamic flag is set to
445
446 false so that the destructor does not try to free memory. Again, the
447
448 compiler does not need to go through the vtable to invoke the destructor.
449
450 */
451
452 Circle_Destructor(&aCircle, FALSE);
453
454 finish=clock();
455 printf("execute time:%f us\n",(double)(finish - start)*1000000/CLOCKS_PER_SEC);
456 }
1 执行结果:
C++代码
draw circle x:50 y:100 radius:25
MoveTo X:100 Y:100
Erase
draw circle x:5 y:5 radius:2
execute time:22000us
C代码
draw circle x:50 y:100 radius:25
MoveTo X:100 Y:100
Erase
draw circle x:5 y:5 radius:2
execute time:7000.000000 us
每次的执行时间会有差异,多次执行的结果C的时间是C++的1/3 ~1/2,因为具体的函数实现中很简单,主要是C++的构造、析构函数的时间比较多(?下面2的结果)。
2 不执行具体的函数,只构造和销毁变量
1 void call2() //不执行函数,只构造和销毁变量
2 {
3 Shape *pShape = new Circle(50, 100, 25);
4 Circle aCircle(5,5, 2);
5 delete pShape;
6 }
7
8 int main()
9 {
10 clock_t start,finish;
11 int i=100000;
12 start = clock();
13 while(i--)
14 {
15 call2();
16 }
17 finish = clock();
18
19 std::cout <<"execute time:" <<( (double)(finish - start)*1000000/CLOCKS_PER_SEC )<<"us"<<std::endl;
20 return 0;
21
22 }
1 void call2() //不执行函数,只构造和销毁变量
2 {
3 Shape *pShape = (Shape *) Circle_Constructor(NULL, 50, 100, 25);
4 Circle aCircle;
5 Circle_Constructor(&aCircle, 5, 5, 2);
6 (pShape->pVTable[2].pFunc)(pShape, TRUE);
7 Circle_Destructor(&aCircle, FALSE);
8 }
9 void main()
10 {
11 int i=100000;
12 clock_t start,finish;
13 start=clock();
14 while(i--)
15 {
16 call2();
17 }
18 finish=clock();
19 printf("execute time:%f us\n",(double)(finish - start)*1000000/CLOCKS_PER_SEC);
20 }
结果:
D:\workspace\HEVC\C++vsC>Circle_C.exe
execute time:38000.000000 us
D:\workspace\HEVC\C++vsC>"Circle_C++.exe"
execute time:39000us
时间基本差不多
3 函数执行为主体
1 void call3() //函数执行为主体
2 {
3 Shape *pShape = new Circle(50, 100, 25);
4 Circle aCircle(5,5, 2);
5 int i=1000000;
6 while(i--);
7 {
8 pShape->Erase();
9 }
10 delete pShape;
11 }
void call3() //函数执行为主体
{
int i=1000000;
Shape *pShape = (Shape *) Circle_Constructor(NULL, 50, 100, 25);
Circle aCircle;
Circle_Constructor(&aCircle, 5, 5, 2);
while(i--)
{
Shape_Erase(pShape);
}
(pShape->pVTable[2].pFunc)(pShape, TRUE);
Circle_Destructor(&aCircle, FALSE);
}
结果:
D:\workspace\HEVC\C++vsC>"Circle_C++.exe"
execute time:3000us
D:\workspace\HEVC\C++vsC>Circle_C.exe
execute time:10000.000000 us
C++比C快,C的时间是C++的3倍。
4 开始的代码C比C++快的原因
1 C code
2 void main()
3 {
4 clock_t start,finish;
5 start=clock();
6 printf("输出比较\n");
7 finish=clock();
8 printf("execute time:%f us\n",(double)(finish - start)*1000000/CLOCKS_PER_SEC);
9 }
10
11 C++ code
12 int main()
13 {
14 clock_t start,finish;
15 start = clock();
16 std::cout<<"输出比较"<<std::endl;
17 finish = clock();
18
19 std::cout <<"execute time:" <<( (double)(finish - start)*1000000/CLOCKS_PER_SEC )<<"us"<<std::endl;
20 return 0;
21
22 }
D:\workspace\HEVC\C++vsC>"Circle_C++.exe"
输出比较
execute time:3000us
D:\workspace\HEVC\C++vsC>Circle_C.exe
输出比较
execute time:1000.000000 us
看来是C的输出函数比C++的效率要高。
5 将1中main中的代码放在call函数中,去掉输出打印函数
C++ code
int main()
{
clock_t start,finish;
int i=10000;
start = clock();
while(i--)
{
call();
}
finish = clock();
std::cout <<"execute time:" <<( (double)(finish - start)*1000000/CLOCKS_PER_SEC )<<"us"<<std::endl;
return 0;
}
C code
void main()
{
clock_t start,finish;
int i=10000;
start = clock();
while(i--)
{
call();
}
finish=clock();
printf("execute time:%f us\n",(double)(finish - start)*1000000/CLOCKS_PER_SEC);
}
D:\workspace\HEVC\C++vsC>"Circle_C++.exe"
execute time:4000us
D:\workspace\HEVC\C++vsC>Circle_C.exe
execute time:4000.000000 us
时间一样。
总结:C++转成C代码后,在类的构造、销毁与C的结构体的构造销毁上,效率没什么区别;而在具体函数的实现上,按照文章的转换方法,C的效率却只有C++的1/3,可以说类的成员函数调用方式比转换成C的直接调用函数的方式效率要高。
但是C的输出函数printf比C++的cout效率要高,时间是C++的1 /3。
原文地址 http://www.eventhelix.com/realtimemantra/basics/ComparingCPPAndCPerformance2.htm