![]() | |
![]() ![]() |
![]() | Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson |
Table of Contents | |
Chapter 4. Data Encapsulation and Value Types |
4.4. Open Types4.4.1 POD Open TypesOpen types are simple types whose member data are publicly accessible, and usually have no methods at all: aggregate types (C++-98: 8.5.1;1) in other words. Consider the uinteger64 type: struct uinteger64 { uint32_t lowerVal; uint32_t upperVal; }; This structure is very simple indeed. However, it's not in the least bit usable, and is, therefore, a perfect example of an open value type since such types are, by and large, pretty unusable. Attempting to use open types as value types is taxing, to say the least. They cannot take part in simple comparison expressions, and manipulating them arithmetically is only possible using manual manipulation of their constituent parts. uinteger64 i1 = . . .; uinteger64 i2 = . . .; bool bLess = i1 < i2; // Compiler error! bool bEqual = i1 == i2; // Compiler error! uinteger64 i3 = i1 + i2; // Compiler error! We should be grateful that C++ rejects all of these operators by default, since at least that affords us protection at compile time. Doing a memberwise comparison would be very dangerous. It might work in most cases for (in)equality, but how would the compiler know which order to prioritize the member variables in a less than comparison? (Note, however, that for backward compatibility with structs, copy construction and copy assignment [see section 2.2] are accepted by the compiler.) Despite the serious usability issues, there are times when we have to use types such as this, usually because we're interfacing to an operating system or library API, for example, to get extended precision arithmetic [Hans1997]. For illustrative purposes, we're going to assume that we've got good reasons for proceeding with our 64-bit integer types, and define an API to deal with them. void UI64_Assign( uinteger64 *lhs, uint32_t higher , uint32_t lower); void UI64_Add( uinteger64 *result, uinteger64 const *lhs , uinteger64 const *rhs); void UI64_Divide( uinteger64 *result, uinteger64 const *lhs , uinteger64 const *rhs); int UI64_Compare(uinteger64 const *lhs, uinteger64 const *rhs); #define UI64_IsLessThan(pi1, pi2) (UI64_Compare(pi1, pi2) < 0) #define UI64_IsEqual(pi1, pi2) (0 == UI64_Compare(pi1, pi2)) #define UI64_IsGreaterThan(pi1, pi2) (0 < UI64_Compare(pi1, pi2)) Using the API, the previous code can be implemented legally: uinteger64 i1 = . . .; uinteger64 i2 = . . .; bool bLess = UI64_IsLessThan(i1, i2); bool bEqual = UI64_IsEqual(i1, i2); uinteger64 i3; UI64_Add(&i3, &i1, &i2); But it's horrible stuff. Naturally C++ let's us do a lot better than this. 4.4.2 C++ Data StructuresBefore we all go rushing to our source databases to convert every struct to a class in an object-oriented blooding frenzy, it's important to emphasize that the open types we've been talking about so far are those where the individual members together represent some logical whole, and whose independent manipulation therefore represents a manifest risk to the logical condition. There are also those open types that are quite safely manipulated in this fashion and that cannot be represented as being a danger. The important distinction is whether individual manipulation of the constituent fields leads to a significant breaking of meaning. Consider the following currency type: struct Currency { int majorUnit; // Dollars, pounds int minorUnit; // Cents, pence }; This is a dangerous open type, since it is possible to add a value to the minorUnit that will make the logical value of a Currency instance invalid. However, the following is an eminently reasonable open type: struct Patron { String name; Currency wallet; }; The name and wallet fields are not intrinsically linked, and a change to the name and/or the wallet fields does not result in a prima facie breaking of the logical coherence of a Patron type. Such types are referred to as (C++) data structures [Stro1997]. Naturally, there's a gray area in these matters, but the two cases above are both clear-cut: one's black and the other's white. ![]() |
![]() | |
![]() ![]() |