Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 4.  Data Encapsulation and Value Types


4.6. Value Types

From the encapsulated types, it is a small step up to what I would call actual value types. In my opinion, the distinguishing characteristic of a value type is that it is EqualityComparable [Aust1999, Muss2001]: it provides meaningful responses, bearing in mind the Langer-Kreft [Lang2002] definition (see section 4.2), to equality and inequality tests. Since the compiler does not provide these operators for class types by default, we need to provide them.

Fundamentally, we need types that do sensible things. The problem with encapsulated types is that they cannot be used in code that makes use of equality comparison. An important use for value types is to be placed in by-value containers, which includes those containers provided by the standard library. Although we can declare and manipulate instances of std::vector<UInteger64>, because it places no restrictions on the uniqueness of the elements it stores, we cannot search for a given type using the standard std::find<>() algorithm:



std::vector<UInteger64>   vi;





vi.push_back(i1);


vi.push_back(i2);


vi.push_back(i3);





std::find( vi.begin(), vi.end()


         , UInteger64(1, 2)); // Error! No op == for UInteger64



(Remember that std::vector<> maintains unordered sequences, so there's also no ordering comparison, and so no < operator, needed either.)

Converting the type to be a full value type is very simple. Since the previous definition of UInteger64 provided an IsEqual() method, we can implement the (in)equality operators in terms of that, as in:



inline bool operator ==(UInteger64 const &i1, UInteger64 const &i2)


{


  return UInteger64::IsEqual(i1, i2);


}


inline bool operator !=(UInteger64 const &i1, UInteger64 const &i2)


{


  return !operator ==(i1, i2);


}



The advantage here is that we've "promoted" the type to be a full value type by the addition of nonmember functions [Meye2000]. Since these functions were previously unavailable, there can be no extant code that depends on their presence (or absence), so we've achieved the enhancement with no maintenance connotations whatsoever. (Author's carp: it is the thoughtless eagerness to push everything into classes that lies behind a great many fat frameworks and C++'s undeserved reputation for inefficiency.)

So, we can now look again at our value-type definition, and propose the following:[5]

[5] Eugene insists that the EqualityComparable aspect is unnecessary, so we can't christen it the Gershnik-Wilson Value Type Definition, however much that might mellifluously trip off the tongue.

Definition: Value Type

Instances cannot polymorphically substitute, or be substituted by, instances of another type at run time.

Instances can be created as, or later made to be, copies of another instance.

Each instance has a logically separate identity. Any change to the logical state of one instance does not result in a change to the logical state of another. (Physical state may be shared according to implementation-specific design decisions, so long as such sharing does not invalidate the logical separation.)

Instances can be (in)equality compared with any other instances, and even with themselves. Equality (and inequality) is reflexive, symmetric, and transitive.



      Previous section   Next section