![]() | |
![]() ![]() |
![]() | Imperfect C++ Practical Solutions for Real-Life Programming By Matthew Wilson |
Table of Contents | |
Chapter 4. Data Encapsulation and Value Types |
4.3. A Taxonomy of Value TypesIn [Stro1997] Bjarne Stroustrup defines value semantics, as opposed to pointer semantics, as being the independence of copied entities. This is a great foundation, but we need more, I think. One of the Imperfect C++ reviewers, Eugene Gershnik, has a language-independent definition of value types. A type is a value type if:
This is an appealing definition, but it is very broad: too broad for my tastes. We'll refine this later in the chapter. One way to look at value types is whether, and by how much, they behave in sensible ways. For example, what should I expect given the following expressions? String str1("Original String"); String str2("Imperfect"); String str3("C++"); char const *cs1 = str1.c_str(); str1 = str2 + " " + str3; // 1 if(!str3) { . . . } // 2 str2.Empty() // 3 ++str; // 4 I would say that expression 1 would concatenate str2, " " and str3, in that order, placing the result into str1, either overwriting, extending, or replacing the storage used to represent "Original String" when str1 was constructed.[2] I would also say that at the point of completion of expression 1 the pointer cs1 is no longer valid, and cannot be used subsequently without undefined behavior. (Of course, if String::c_str() was temporary [see section 16.2], this wouldn't be a problem, since the assignment would not be allowed.)
Expression 2 would likely be interpreted to mean if str3 is "not" then the contents of the block should be executed. Note that what it means to be "not" is up for debate: It may mean no contents, or that the contents contain the empty string "", or both. It could even mean that the string contains "false"! Such an expression is ambiguous, and ambiguity is the enemy of both correctness and maintainability. Sadly, I've seen this very thing in production code. The third expression could mean: empty str2 of its contents. However, it could also mean: return a value indicating whether or not str2 is empty. Given the choice, I would always go for the former. (Types and variables are nouns; methods and functions are verbs.) Alas the standard library disagrees, and it can be hard to disagree with the standard library.[3] Expression 4 is meaningless. I cannot think of a sensible way in which a string in C++ can be incremented.[4] (See Appendix B to see evidence of a time long, long ago in a galaxy far, far away when this was not the case.)
For built-in types, expected behavior is easy, as it is already prescribed and inviolable. It is our responsibility, therefore, to ensure that our types operate as expected with the operators for which they are defined. If you write an extended-precision integer type whose operator -=() performs modulus division, you'll be hunted down. Types intended to be treated as values should, as much as is possible, behave "as the ints do" [Meye1996]. In the remainder of the chapter, we investigate what I see as a spectrum of value type concepts. I suggest there are four levels:
Depending on your point of view, they are all value types, or only the last two. Whichever way you look at it, however, they're in there because they represent recognized steps in the spectrum and are used in the real world. ![]() |
![]() | |
![]() ![]() |