tag 标签: classes

相关博文
  • 热度 19
    2013-1-15 19:27
    3434 次阅读|
    0 个评论
    Last year, I wrote a number of articles on how virtual functions behave in C++ and how you can obtain similar behaviour in C. In short, 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). Last month, I introduced the concept of pure virtual functions in C++. This month, I'll show how compilers typically implement pure virtual functions by showing how you can achieve similar behaviour in C. 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. 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 = 0; virtual double perimeter() const = 0; ~~~ private: color outline, fill; }; The characters = 0 appearing at the end of the declarations for area and perimeter indicates that they are pure virtual functions. They need not be defined. As I asked at the end of my previous column, if shape's area and perimeter functions are undefined, what happens when you try to call them? Pure virtual function virtually impossible C++ makes calling a pure virtual function nearly impossible. A class with at least one pure virtual function is called an abstract class . C++ doesn't let you create an abstract class object except as the base class subobject of some derived class object. For example, these declarations won't compile: shape s; // compile error shape x ; // compile error You can declare a pointer to a shape, as in: shape *ps; // OK However, the compiler will reject a new-expression that tries to construct an abstract base class object, as in: ps = new shape; // compiler error Again, any class with at least one pure virtual function is an abstract class. If you can't create any such objects, then you don't have to worry that you might call a pure virtual function. Unfortunately, C++ compilers have trouble detecting all possible calls to pure virtual functions, such as when the constructor of an abstract class indirectly calls a pure virtual function of the same class. Thus, most compilers provide another line of defence. More on this shortly. The definition for our 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; }; If you don't declare the area and perimeter functions in circle, then circle will inherit them as pure virtual functions, and circle will be an abstract class. You won't be able to create any circle objects. However, the class definition above does declare area and perimeter, so they're not pure virtual and circle isn't an abstract class. Now you're on the hook to define circle's area and perimeter functions. Inasmuch as circle is not an abstract class, you can create circle objects, as in: circle c (3); or: shape *ps = new circle (4); If the compiler could detect and prevent all calls to pure virtual functions, implementing pure virtual functions would be trivial—simply initialise the vtbl entry for each pure virtual function as a null pointer. In C, the code to define the vtbl for the shape base class would look something like: typedef struct shape_vtbl shape_vtbl; struct shape_vtbl { double (*area)(shape const *); double (*perimeter)(shape const *); }; shape_vtbl the_shape_vtbl = { 0, // 0 converted to null pointer 0 // ditto }; A second line of defence Unfortunately, compilers may fail to catch some pure virtual function calls. Such failures are rare, but nevertheless possible. As a second line of defence, if a pure virtual function has no definition, most compilers generate a definition for that function that makes a stink if executed. The C code to do this for the shape class might look like: double shape_area(shape const *s) { pure_virtual_alert(); return 0; } double shape_perimeter(shape const *s) { pure_virtual_alert(); return 0; } ~~~ shape_vtbl the_shape_vtbl = { shape_area, shape_perimeter }; On a desktop system, the pure_virtual_alert function might simply write a message to the system's stderr stream or display a message box, and then terminate the program. For an embedded system, you would have to tailor the response to be something more appropriate. Endnotes: Saks, Dan, "Implementing virtual functions in C," Eetindia.co.in, August 14, 2012. www.embedded.com/4391967. Saks, Dan, "The concept of pure virtual functions," Eetindia.co.in, December 20, 2012. www.embedded.com/4403313.  
  • 热度 11
    2011-9-7 22:34
    1944 次阅读|
    0 个评论
    Classes are commonly the best tool for modeling memory-mapped devices  In C++. 1 You can use a class to hide messy details, such as device register layouts and bit masks, behind a simpler and more robust interface. A constructor is a special class member function that provides guaranteed initialization for objects of its class type. 2 Using constructors to initialize objects is common practice in C++. C++ programmers should reasonably expect classes for memory-mapped devices to do initialization via constructors, unless there's some compelling reason to do otherwise. In many embedded systems, the appropriate way to initialize a device is to put it into an inactive state. For example, the constructor for a programmable timer might simply make sure that the timer is disabled, as in: class timer_type { public: ~~~ timer_type() { disable(); } ~~~ }; In a previous article, I explained the problem with getting constructors for memory-mapped devices to execute automatically. 3 This month, I'll explore one way to overcome it. Recapping the problem With most C and C++ compilers, you can name a memory-mapped object using a standard extern declaration such as: extern timer_type the_timer; and then use the linker to map the_timer to the desired address. However, this declaration is not a definition, so the compiler doesn't generate code to allocate storage or call a constructor. Some C and C++ compilers provide a nonstandard language extension that lets you position an object at a specified memory address, such as: timer_type the_timer @ 0xFFFF6000; With most compilers that support this sort of declaration, this isn't a definition, so the compiler won't generate a constructor call for this, either. Defining a pointer or reference to a memory-mapped object, such as: timer_type the_timer = *reinterpret_cast(0xFFFF6000); has the same initialization problem as the previous object declarations. This reference definition doesn't define the memory-mapped object itself. Here, too, the compiler won't generate a constructor call applied to the memory-mapped object. If the compiler won't call a constructor implicitly, why not just write an explicit call, say, immediately after the object or reference declaration? That is, if you declare the timer object as: extern timer_type the_timer; why not write an explicit constructor call to go with it, such as: the_timer.timer_type(); // ? Because it won't compile. C++ won't let you call a constructor as if it were any other class member function. In taking on the job of generating constructor calls automatically, C++ denies you the ability to do it yourself. Well, almost. In truth, although you can't call a constructor using the usual member function call syntax, you can call a constructor using a particular form of new-expression known as placement new-expression . To appreciate how it works, let's first review new-expressions in general. New-expressions In C++, you typically allocate dynamic storage using a new-expression such as: pt = new T; where T is a type and pt is an object of type "pointer to T ". When T is a class type, the new-expression not only allocates storage, but also invokes a constructor. Thus, using new is generally preferable to using the standard malloc function. Whereas malloc allocates raw storage of indeterminate value, new can create objects with coherent initial values. A new-expression allocates memory by calling a function named operator new . Each C++ environment provides a default global allocation function declared as: void *operator new(std::size_t n); As with malloc , the argument to operator new is the size (in bytes) of the storage request, and the return value is the address of the allocated storage. However, operator new reports failure differently from malloc . Whereas malloc returns a null pointer if it can't allocate the requested storage, operator new throws an exception. 4 Thus, for a class type T , a new-expression such as: pt = new T; translates more-or-less into something like: pt = static_cast(operator new(sizeof(T))); pt-T(); The first statement acquires storage for a T object by calling operator new , and converts the address of that storage from type void * to type T * . The second initializes the storage by applying T 's default constructor. As I mentioned earlier, a C++ compiler won't let you write this explicit constructor call, but it's happy to do it for you. If class T has a constructor that accepts arguments, you can get the new-expression to call that constructor by providing the constructor arguments as part of the new-expression, as in: pt = new T (x, y, z);. In this case, the new-expression translates more-or-less into something like: pt = static_cast(operator new(sizeof(T))); pt-T(x, y, z); Overloading operator new As with any other function, you can overload operator new by simply declaring additional functions with the same name but different parameter types. For example, in addition to the standard allocation function: void *operator new(std::size_t n); you might declare: void *operator new(std::size_t n, other_info i); where other_info is some user-defined type for conveying additional information to the allocation function. That's simple enough, but how can you get a new-expression to use this alternative allocation function? The problem is finding some way to pass the additional argument to operator new . You can't add a parenthesized argument list after the type name in the new-expression because the compiler will interpret that list as arguments to a constructor, not as arguments to an operator new . That is: pt = new T (x); passes x as an argument to a T constructor, not as an additional argument to operator new . Rather, you must squeeze the additional argument(s) to the allocation function into some other spot in the new-expression. That spot is between the keyword new and the allocated type. For example, the new-expression in: other_info info; ~~~ pt = new (info) T; specifies info as an additional argument to operator new . As always, the new-expression uses sizeof(T) as the first argument to operator new . Thus, the new-expression results in a call to: operator new(sizeof(T) , info ).  
  • 热度 10
    2011-9-7 22:26
    3365 次阅读|
    0 个评论
    In C++, the best tool for modeling memory-mapped devices are usually classes. 1 You can use a class to hide messy details, such as device register layouts and bit masks, behind a simpler and more robust interface. A constructor is a special class member function that provides guaranteed initialization for objects of its class type. 2 Using constructors to initialize objects is common practice in C++. C++ programmers should reasonably expect classes for memory-mapped devices to do initialization via constructors, unless there's some compelling reason to do otherwise. In many embedded systems, the appropriate way to initialize a device is to put it into an inactive state. For example, the constructor for a programmable timer might simply make sure that the timer is disabled, as in: class timer_type { public: ~~~ timer_type() { disable(); } ~~~ }; In a previous article, I explained the problem with getting constructors for memory-mapped devices to execute automatically. 3 This month, I'll explore one way to overcome it. Recapping the problem With most C and C++ compilers, you can name a memory-mapped object using a standard extern declaration such as: extern timer_type the_timer; and then use the linker to map the_timer to the desired address. However, this declaration is not a definition, so the compiler doesn't generate code to allocate storage or call a constructor. Some C and C++ compilers provide a nonstandard language extension that lets you position an object at a specified memory address, such as: timer_type the_timer @ 0xFFFF6000; With most compilers that support this sort of declaration, this isn't a definition, so the compiler won't generate a constructor call for this, either. Defining a pointer or reference to a memory-mapped object, such as: timer_type the_timer = *reinterpret_cast(0xFFFF6000); has the same initialization problem as the previous object declarations. This reference definition doesn't define the memory-mapped object itself. Here, too, the compiler won't generate a constructor call applied to the memory-mapped object. If the compiler won't call a constructor implicitly, why not just write an explicit call, say, immediately after the object or reference declaration? That is, if you declare the timer object as: extern timer_type the_timer; why not write an explicit constructor call to go with it, such as: the_timer.timer_type(); // ? Because it won't compile. C++ won't let you call a constructor as if it were any other class member function. In taking on the job of generating constructor calls automatically, C++ denies you the ability to do it yourself. Well, almost. In truth, although you can't call a constructor using the usual member function call syntax, you can call a constructor using a particular form of new-expression known as placement new-expression . To appreciate how it works, let's first review new-expressions in general. New-expressions In C++, you typically allocate dynamic storage using a new-expression such as: pt = new T; where T is a type and pt is an object of type "pointer to T ". When T is a class type, the new-expression not only allocates storage, but also invokes a constructor. Thus, using new is generally preferable to using the standard malloc function. Whereas malloc allocates raw storage of indeterminate value, new can create objects with coherent initial values. A new-expression allocates memory by calling a function named operator new . Each C++ environment provides a default global allocation function declared as: void *operator new(std::size_t n); As with malloc , the argument to operator new is the size (in bytes) of the storage request, and the return value is the address of the allocated storage. However, operator new reports failure differently from malloc . Whereas malloc returns a null pointer if it can't allocate the requested storage, operator new throws an exception. 4 Thus, for a class type T , a new-expression such as: pt = new T; translates more-or-less into something like: pt = static_cast(operator new(sizeof(T))); pt-T(); The first statement acquires storage for a T object by calling operator new , and converts the address of that storage from type void * to type T * . The second initializes the storage by applying T 's default constructor. As I mentioned earlier, a C++ compiler won't let you write this explicit constructor call, but it's happy to do it for you. If class T has a constructor that accepts arguments, you can get the new-expression to call that constructor by providing the constructor arguments as part of the new-expression, as in: pt = new T (x, y, z);. In this case, the new-expression translates more-or-less into something like: pt = static_cast(operator new(sizeof(T))); pt-T(x, y, z); Overloading operator new As with any other function, you can overload operator new by simply declaring additional functions with the same name but different parameter types. For example, in addition to the standard allocation function: void *operator new(std::size_t n); you might declare: void *operator new(std::size_t n, other_info i); where other_info is some user-defined type for conveying additional information to the allocation function. That's simple enough, but how can you get a new-expression to use this alternative allocation function? The problem is finding some way to pass the additional argument to operator new . You can't add a parenthesized argument list after the type name in the new-expression because the compiler will interpret that list as arguments to a constructor, not as arguments to an operator new . That is: pt = new T (x); passes x as an argument to a T constructor, not as an additional argument to operator new . Rather, you must squeeze the additional argument(s) to the allocation function into some other spot in the new-expression. That spot is between the keyword new and the allocated type. For example, the new-expression in: other_info info; ~~~ pt = new (info) T; specifies info as an additional argument to operator new . As always, the new-expression uses sizeof(T) as the first argument to operator new . Thus, the new-expression results in a call to: operator new(sizeof(T) , info ).    
相关资源
  • 所需E币: 0
    时间: 2020-6-29 16:58
    大小: 414.94KB
    上传者: Argent
    号外号外!有兴趣学习硬件画PCB板的网友吗?硬件设计工程师必学的课程,常见的画板工具有AltiumDesigner,protel99,pads,orcad,allegro,EasyEDA等,此次分享的主题是使用AltiumDesigner设计你的硬件电路,万丈高楼平地起,硬件的积累至关重要。花钱收藏的AltiumDesigner资料难道不香吗?下载资料学习学习吧,希望能帮助到你。