热度 17
2013-2-22 11:22
3202 次阅读|
0 个评论
For several months now, I've been discussing polymorphic types and virtual functions. I explained how to use virtual functions in C++ as well as how to implement and use virtual functions in C. , More specifically, I showed how to emulate a polymorphic C++ class (a class with at least one virtual function) as a C structure that has an additional member commonly called a vptr (VEE-pointer). The vptr points to a table of function pointers called a vtbl (VEE-table). Last November, I showed how to initialise derived polymorphic objects. My colleague, Miro Samek, raised a very interesting question about one of my implementation choices, and offered an alternative approach. While his alternative has merit, I still think my approach is preferable and I'll try to explain why. In any event, it's worth considering both techniques because the discussion raises interesting questions about interface design and maintainability. I'll begin this month by explaining Miro's question in detail. Once again, my sample classes represent an assortment of two-dimensional geometric shapes such as circle, rectangle, and triangle, all derived from a common base class called shape. The C++ definition for the shape base class looks in part like: class shape { public: shape(color o, color f); // constructor virtual double area() const; virtual double perimeter() const; private: color outline, fill; }; In C, the comparable declarations look like: // shape.h—a C base class for shapes #ifndef SHAPE_H_INCLUDED #define SHAPE_H_INCLUDED typedef struct shape shape; typedef struct shape_vtbl shape_vtbl; struct shape_vtbl { double (*area)(shape const *s); double (*perimeter)(shape const *s); }; struct shape { shape_vtbl *vptr; color outline, fill; }; void shape_construct(shape *s, color o, color f); double shape_area(shape const *s); double shape_perimeter(shape const *s); #endif In C++, the definition for a circle class derived from shape looks like: class circle: public shape { public: circle(double r, color o, color f); // constructor virtual double area() const; virtual double perimeter() const; ~~~ private: double radius; }; I rendered the derived class in C using the following declarations: // circle.h – a C class for circle derived from shape #ifndef CIRCLE_H_INCLUDED #define CIRCLE_H_INCLUDED #include "shape.h" typedef struct circle circle; struct circle { shape base; // the base class subobject double radius; }; void circle_construct(circle *c, double r, color o, color f); double circle_area(circle const *c); double circle_perimeter(circle const *c); #endif When I showed this header in previous articles, I omitted the declarations for circle_area and circle_perimeter (shown above in red ). I didn't need them in the prior discussion, so I left them out for brevity. However, in general, they should be there. The corresponding C source file looks in part like: // circle.c—circle implementation ~~~ #include "circle.h" ~~~ typedef struct circle_vtbl circle_vtbl; struct circle_vtbl { double (*area)(circle const *); double (*perimeter)(circle const *); }; static circle_vtbl the_circle_vtbl = { circle_area, circle_perimeter }; void circle_construct(circle *c, double r, color o, color f) { shape_construct(c-base, o, f); c-base.vptr = (shape_vtbl *)the_circle_vtbl; c-radius = r; } If you want circle_construct to be an inline function or function-like macro, you must move its definition to the circle.h header, along with the circle_vtbl structure declaration. You also have to remove the keyword static from the definition for the_circle_vtbl, and add an extern declaration for it to the header. As I explained in an earlier column, a derived class inherits all the data members of its base class. Each inherited member must have the same offset within the derived class as it does in the base class. The simplest and most straightforward way to ensure this is to define a base class object as the first member of the derived class, just as I did previously: typedef struct circle circle; struct circle { shape base; // the base class subobject double radius; }; The derived class vtbl inherits all the members of its base class vtbl, and again each inherited member must have the same offset in the derived class vtbl as it does in the base class vtbl. Thus, it seems appropriate to use the same technique for implementing the derived class vtbl. That is, declare a base class vtbl object as the first member of the derived class vtbl, as in: typedef struct circle_vtbl circle_vtbl; struct circle_vtbl { shape_vtbl base; // base class vtbl // virtual functions introduced in circle }; That's what Miro suggested, but that's not what I did. Rather, I defined each member of the derived class vtbl individually to correspond to the members in base class. That is, I defined the base class vtbl structure as: typedef struct shape_vtbl shape_vtbl; struct shape_vtbl { double (*area)(shape const *s); double (*perimeter)(shape const *s); }; and I defined the derived class vtbl structure as: typedef struct circle_vtbl circle_vtbl; struct circle_vtbl { double (*area)(circle const *); double (*perimeter)(circle const *); }; Therein lies Miro's question: Why did I declare the inherited vtbl members individually rather than declare a base class vtbl object as the first member of the derived class vtbl? I'll answer the question next time. In the meantime, you might want to ponder why the corresponding members in the base and derived class vtbl are not exactly the same. References Saks, Dan, "Understanding virtual functions in C++," Eetindia.co.in, April 7, 2012. http://forum.eetindia.co.in/BLOG_ARTICLE_11885.HTM. Saks, Dan, "Implementing virtual functions in C," Eetindia.co.in, August 14, 2012. http://forum.eetindia.co.in/BLOG_ARTICLE_13544.HTM. Saks, Dan, "Learn to initialise derived polymorphic objects," Eetindia.co.in, November 22, 2012. http://forum.eetindia.co.in/BLOG_ARTICLE_14949.HTM. Ibid . Saks, Dan, "Setting storage layout for polymorphic objects," Eetindia.co.in, July 27, 2012. http://forum.eetindia.co.in/BLOG_ARTICLE_13258.HTM.