|
|
In our hypothetical application, we assumed that the constant Time::MIN is a more appropriate null value for Time than the value of the parameterless constructor. This section illustrates how to change the default definition of null for user-defined types.
First, we specify, via the null attribute, the expression Time::MIN:
Time USER .null Time::MIN
Since the default header attribute is Time.h, and since Time::MIN is defined in Time.h, this definition satisfies the requirement that the null attribute be a well-defined C++ expression of type Time under the header file closure. This would end our consideration of the issue of null values, were it not for one additional (and somewhat subtle) issue: what method should be used to test for null values?
Recall that a typed inserter only inserts data members into the output stream if their values are non-null. For builtin types, null values are (for strings) the empty string and (for integral values) zero. We will see how to change these definitions later in this section.
The null tests for these builtin types are hard-coded into switch statements in the inserter. How is the analogous test implemented for user-defined types? In terms of our example, how does the usr inserter tell whether the last_login member is null?
The answer is that the inserter can use either of two methods:
To illustrate the second method, assume that we will add a Stack field to the usr record. Assume further that (1) class Stack is defined in file Stack.h, (2) Stack has a parameterless constructor that creates an empty Stack, (3) Stack() is acceptable as the null value, and (4) Stack does not have an equality test, but (5) Stack does have a member function named height() that returns the number of elements in the stack.
We could define the Stack USER type as follows:
usr.g Stack USER .header Stack.h .header Stacknull.h .isnull is_empty
implying a contract that function is_empty is declared somewhere in the header file closure implied by header attributes. In fact, we have declared the function in Stacknull.h and defined it in Stacknull.c
Stacknull.h int is_empty(const Stack& s); Stacknull.c int is_empty(const Stack& s){ return s.height()==0; }
Each time it needs to test whether a given Stack is null, the typed inserter will call is_empty() with the Stack in question.