tag 标签: objects

相关博文
  • 热度 21
    2012-12-20 18:43
    3030 次阅读|
    0 个评论
    In the past several months, I have discussed polymorphic types and virtual functions. I showed how to implement virtual functions in C in a way that generates machine code similar to what you get with virtual function 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 month, I showed how to initialise the vptr in base class objects . This month, I'll look at initializing derived class objects. 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 As I showed last month, you can define the shape vtbl object in a source file that also defines the member functions of the shape "class": // shape.c—a C base class for shapes #include "shape.h" ~~~ static shape_vtbl the_shape_vtbl = { shape_area, shape_perimeter }; void shape_construct(shape *s, color o, color f) { s-vptr = the_shape_vtbl; s-outline = o; s-fill = f; } 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; }; Derivation defines an "is a" or "is a kind of" relationship between the derived and base class. That is, it lets you substitute a derived class object, such as a circle or rectangle, for a base class shape object. For example, given a C++ function such as: void f(shape *p) { ~~~ p-perimeter(); // virtual call to shape's perimeter ~~~ } you can pass it a derived class object, as in: circle c; ~~~ f(c); // pass a circle as a shape and it computes the circle's perimeter correctly. With a little more effort, you can emulate this behaviour in C. In C, you have to explicitly mention the vptr in virtual function calls, as in: void f(shape *p) { ~~~ p-vptr-perimeter(p); virtual call to shape's perimeter ~~~ } You also need an explicit cast to convert a "pointer to derived" into "pointer to base", as in: circle c; ~~~ f((shape *)c); // pass a circle as a shape This substitution works (f will compute the circle's perimeter correctly) only if the vptr has the same offset in the derived class object as it does in a base class object. The easiest way to satisfy this requirement is to implement each derived class type in C as a structure whose first member has the base class type, as in: // 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); #endif The base member of the circle structure above includes all the members inherited from the shape base class, including vptr. The definition for the circle_construct function appears, along with the circle vtbl object, in a separate source file: // circle.c—circle implementation ~~~ #include "circle.h" double circle_area(circle const *c) { return PI * c-radius * c-radius; } double circle_perimeter(circle const *c) { return 2 * PI * c-radius; } 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; } The circle_construct function implements behaviour comparable to a C++ constructor. It calls the shape_construct function to initialise the base class part. However, shape_construct sets the vptr to point to shape's vtbl, which is correct for the base class shape, but not for the derived class circle. Thus, circle_construct needs to reassign the vptr to point to circle's vtbl. This assignment requires a cast because circle_vtbl isn't exactly the same type as shape_vtbl. The two structures have the same memory layout, but the corresponding pointers in the different structures point to functions with slightly different types. Thus, the derived class constructor contains two assignments to the vptr. The second assignment completely overwrites the value assigned by the first. Ideally, the compiler will "optimise away" the first assignment. However, the compiler can do this optimisation only if it can see both assignments in the context of the derived class constructor, which it can do only if the base class constructor is an inline function. If you define the shape_construct function as inline, you should move the function definition to the shape.h header file. When you do that, you must also give the_shape_vtbl external linkage by removing the keyword static from its definition, as in: // shape.c—a C base class for shapes ~~~ shape_vtbl the_shape_vtbl = { // used to be static shape_area, shape_perimeter }; ~~~ If your C compiler supports the inline keyword (from C99), then the shape.h header would look in part like: // shape.h—a C base class for shapes ~~~ typedef struct shape shape; ~~~ typedef struct shape_vtbl shape_vtbl; struct shape_vtbl { double (*area)(shape const *s); double (*perimeter)(shape const *s); }; extern shape_vtbl the_shape_vtbl; struct shape { shape_vtbl *vptr; color outline, fill; }; inline void shape_construct(shape *s, color o, color f) { s-vptr = the_shape_vtbl; s-outline = o; s-fill = f; } If your compiler doesn't support the keyword inline, then you can implement the constructor as a macro: #define shape_construct(s, o, f) ( \ (s)-vptr = the_shape_vtbl, \ (s)-outline = (o), \ (s)-fill = (f) \ ) Either way, a compiler with a decent optimiser should eliminate the redundant assignment to the vptr. Once again, you don't have to worry about any of this in C++. With C++, the compiler automatically generates code to initialise the vptr properly.
  • 热度 17
    2012-11-22 20:55
    2842 次阅读|
    0 个评论
    I've previously discussed polymorphic types and virtual functions. I have also discussed how to implement virtual function in C in a way that generates machine code similar to what you get with virtual functions in C++. In particular, I showed that you can 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). Of course, virtual function calls work only if you initialize each vptr properly. That initialization is my topic in this article. 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 . In C++, the definition for the base class shape looks in part like: class shape { public: shape(); // constructor virtual double area() const; virtual double perimeter() const; private: color outline, fill; }; As at least one reader has observed, the virtual functions in the shape class should probably be pure virtual functions and the destructor should be virtual. I haven't covered these topics yet, and they're not crucial to this discussion. You can implement a polymorphic shape type in C using the following declarations: // shape.h—a 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); double shape_area(shape const *s); double shape_perimeter(shape const *s); #endif      Each shape object has a vptr that must be initialized to point to a vtbl object. Every shape that actually is a shape (rather than a circle , rectangle or triangle ) uses the same virtual functions, so the program needs only one vtbl object for every shape . In C, the vtbl object should be defined in the source file that also defines the member functions of the shape "class": // shape.c—a base class for shapes #include "shape.h" double shape_area(shape const *s) { /* compute and return something */; } double shape_perimeter(shape const *s) { /* compute and return something */; } static shape_vtbl the_shape_vtbl = { shape_area, shape_perimeter }; void shape_construct(shape *s) { s-vptr = the_shape_vtbl; } The definition for the_shape_vtbl has an initializer that specifies a value for each function pointer. The shape_construct function shown above initializes a shape object's vptr to point to the_shape_vtbl . You might want to augment that function to initialize other data members in a shape , as in: void shape_construct(shape *s) { s-vptr = the_shape_vtbl; s-outline = /* an appropriate default */; s-fill = /* an appropriate default */; } or as: void shape_construct(shape *s, color o, color f) { s-vptr = the_shape_vtbl; s-outline = o; s-fill = f; } In C++, you don't declare the vptr member and you don't write any code to initialize it. The compiler does all that automatically. If you choose not to initialize the outline and fill members explicitly, you could define the shape constructor in C++ as just: shape::shape() { } The compiler automatically generates code in this constructor to initialize the shape 's vptr. The constructor also initializes the outline and fill members to their default values (which might be no initialization at all). Moreover, in C++, you don't even have to write the constructor. If you don't declare a shape constructor at all, the compiler will generate one for you that has the same effect as the one defined just above. In C++, you can define a shape object such as: void f() { shape s; ~~~ and the compiler automatically generates a call to the shape constructor to initialize the vptr. In C, you have to write that call explicitly, as in: void f() { shape s; shape_construct(s); ~~~ If you dynamically allocate a shape in C++ using: void f() { shape *ps = new shape; ~~~ the compiler automatically generates a constructor call to initialize that shape 's vptr. Again, in C you have to write that call explicitly, as in: void f() { shape *ps = malloc(sizeof(shape)); shape_construct(ps); ~~~ As I explained in that prior column, in C++, a call to the virtual area function applied to a shape looks exactly like a non-virtual call, as in: ps-area(); In C, the equivalent call looks like: ps-vptr-area(ps); A properly-initialized vptr makes it happen.
  • 热度 18
    2012-10-24 18:13
    3296 次阅读|
    0 个评论
    On and off for the past year, I've been discussing polymorphic types and virtual functions. A couple of months ago, I showed how to implement virtual functions in C in a way that generates machine code similar to what you get with virtual functions in C++. In particular, I showed that you can 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). Of course, virtual function calls work only if you initialise each vptr properly. That initialisation is my topic this month. As in my prior articles, 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 . In C++, the definition for the base class shape looks in part like: class shape { public: shape(); // constructor virtual double area() const; virtual double perimeter() const; private: color outline, fill; }; As at least one reader has observed, the virtual functions in the shape class should probably be pure virtual functions and the destructor should be virtual. I haven't covered these topics yet, and they're not crucial to this discussion. You can implement a polymorphic shape type in C using the following declarations: // shape.h—a 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); double shape_area(shape const *s); double shape_perimeter(shape const *s); #endif      Each shape object has a vptr that must be initialized to point to a vtbl object. Every shape that actually is a shape (rather than a circle , rectangle or triangle ) uses the same virtual functions, so the program needs only one vtbl object for every shape . In C, the vtbl object should be defined in the source file that also defines the member functions of the shape "class": // shape.c—a base class for shapes #include "shape.h" double shape_area(shape const *s) { /* compute and return something */; } double shape_perimeter(shape const *s) { /* compute and return something */; } static shape_vtbl the_shape_vtbl = { shape_area, shape_perimeter }; void shape_construct(shape *s) { s-vptr = the_shape_vtbl; } The definition for the_shape_vtbl has an initializer that specifies a value for each function pointer. The shape_construct function shown above initializes a shape object's vptr to point to the_shape_vtbl . You might want to augment that function to initialise other data members in a shape , as in: void shape_construct(shape *s) { s-vptr = the_shape_vtbl; s-outline = /* an appropriate default */; s-fill = /* an appropriate default */; } or as: void shape_construct(shape *s, color o, color f) { s-vptr = the_shape_vtbl; s-outline = o; s-fill = f; } In C++, you don't declare the vptr member and you don't write any code to initialise it. The compiler does all that automatically. If you choose not to initialise the outline and fill members explicitly, you could define the shape constructor in C++ as just: shape::shape() { } The compiler automatically generates code in this constructor to initialise the shape 's vptr. The constructor also initializes the outline and fill members to their default values (which might be no initialisation at all). Moreover, in C++, you don't even have to write the constructor. If you don't declare a shape constructor at all, the compiler will generate one for you that has the same effect as the one defined just above. In C++, you can define a shape object such as: void f() { shape s; ~~~ and the compiler automatically generates a call to the shape constructor to initialise the vptr. In C, you have to write that call explicitly, as in: void f() { shape s; shape_construct(s); ~~~ If you dynamically allocate a shape in C++ using: void f() { shape *ps = new shape; ~~~ the compiler automatically generates a constructor call to initialise that shape 's vptr. Again, in C you have to write that call explicitly, as in: void f() { shape *ps = malloc(sizeof(shape)); shape_construct(ps); ~~~ As I explained in that prior column, in C++, a call to the virtual area function applied to a shape looks exactly like a non-virtual call, as in: ps-area(); In C, the equivalent call looks like: ps-vptr-area(ps); A properly-initialized vptr makes it happen. I'll have more to say about virtual functions in future columns.  
相关资源