C++ for Scientists - Technische Universität Dresden
C++ for Scientists - Technische Universität Dresden C++ for Scientists - Technische Universität Dresden
84 CHAPTER 3. CLASSES Approach 3: Returning proxies Instead of returning a pointer we can build a specific type that keeps a reference to the matrix and the row index and that provide an operator[] for accessing matrix entries. This proxy must be therefore a friend of the matrix class to reach its private data. Alternatively, we can keep the operator with the parentheses and call this one from the proxy. In both cases, we encounter cyclic dependencies. 10 If we have several matrix types, each of them would need its own proxy. We would also need different proxies for constant and mutable access respectively. In Section 6.5 we will show how to write a proxy that works for all matrix types. The same templated proxy will handle constant and mutable access. Fortunately, it even solves the problem of mutual dependencies. The only minor flaw is that eventual errors cause lenghty compiler messages. Approach 4: Multi-index type (advanced) Preliminary note: this approach contains several new language features and discusses some subtle details. If you do not understand the first time, don’t worry. If you like to skip it, do it. That will not be a problem for understanding the rest of the book. But please read the comparing discussion. The fact that operator[] accepts only one argument does not necessarily mean that we cannot give two. But we need a tricky technique to build one object out of two, without explicitly constructing the object. The implementation is based on the matrix example from an onlinetutorial [Sch]. First, we define a type: struct double index { double index (int i1, int i2): i1 (i1), i2 (i2) {} int i1, i2; }; For this type we define the access operator: double& operator[](double index i) { return data[i.i1∗ncols + i.i2]; } const double& operator[](double index i) const { return data[i.i1∗ncols + i.i2]; } Now we can write: A[double index(1, 0)]; This works but it was not the concise notation we were looking for. We introduce a second type: struct single index { single index (int i1): i1 (i1) {} double index operator, (single index j) const { 10 The dependencies cannot be resolved with forward declaration because we not only define references or pointers but call member functions in the matrix and in the proxy. We will explain this in § ??.
3.7. ACCESSING OBJECT MEMBERS 85 }; } return double index (i1, j.i1); operator int() const { return i1; } single index& operator++ () { ++i1; return ∗this; } int i1; This new type overloaded the comma operator so that a second index creates a double index. The constructor is implicit and the class contains an operator to int. This enables the compiler to switch between single index and int in both ways. This allows us to write code like: or single index i= 0, j= 1; std::cout ≪ ”A[0, 1] is ” ≪ A[i, j] ≪ ’\n’; for (single index i= 0; i < A.num rows(); ++i) for (single index j= 0; j < A.num cols(); ++j) std::cout ≪ ”A[” ≪ i ≪ ”, ” ≪ j ≪ ”] is ” ≪ A[i, j] ≪ ’\n’; In the loop, an single index (i) is compared with an int (A.num rows()). This comparison operator is not defined. The compiler converts i implicitly to an int and compares the values as int. Thus, the conversion operator allows us to use all operations that are defined for int without implementing them. At this opportunity we can introduce another operator. C and C ++ provide a prefix and postfix increment/decrement. The difference only manifests if we read the incremented/decremented value, e.g., j= i++; is differs from j= ++i; by having the old value of i in j (in the first statement) or the already incremented i (in the second statement). If the increment is the only expression in the statement, e.g., i++; or ++i;, there is no semantic difference. Therefore, it does not matter for loops whether we use the postfix or prefix notation. for (single index i= 0; i < A.num rows(); ++i) is (semantically) equivalent to: for (single index i= 0; i < A.num rows(); i++) For C ++ integer types it really does not matter. For user-defined types, the compiler will tell us that this operation is not defined. The GNU Compiler emits the following error message: no ≫operator++(int)≪ for suffix ≫++≪ declared, instead prefix operator tried Fortunately, it already reveals the solution. The operator++ without arguments is understood as prefix operator. To define a postfix operator we must define it with a dummy int argument. This argument has no effect but we need a way to define the symbol ++ as prefix and postfix operator. Unary operators are defined as member functions without argument. This works for all other unary operators but in case
- Page 34 and 35: 34 CHAPTER 2. C++ BASICS } else if
- Page 36 and 37: 36 CHAPTER 2. C++ BASICS eps/= 2.0;
- Page 38 and 39: 38 CHAPTER 2. C++ BASICS for (...;
- Page 40 and 41: 40 CHAPTER 2. C++ BASICS 2.6.1 Inli
- Page 42 and 43: 42 CHAPTER 2. C++ BASICS To make su
- Page 44 and 45: 44 CHAPTER 2. C++ BASICS float divi
- Page 46 and 47: 46 CHAPTER 2. C++ BASICS The first
- Page 48 and 49: 48 CHAPTER 2. C++ BASICS #ifndef at
- Page 50 and 51: 50 CHAPTER 2. C++ BASICS float A[7]
- Page 52 and 53: 52 CHAPTER 2. C++ BASICS Encapsulat
- Page 54 and 55: 54 CHAPTER 2. C++ BASICS As a pract
- Page 56 and 57: 56 CHAPTER 2. C++ BASICS A function
- Page 58 and 59: 58 CHAPTER 2. C++ BASICS Now that t
- Page 60 and 61: 60 CHAPTER 2. C++ BASICS we need sm
- Page 62 and 63: 62 CHAPTER 2. C++ BASICS 2.12 Exerc
- Page 64 and 65: 64 CHAPTER 2. C++ BASICS 2.13 Opera
- Page 66 and 67: 66 CHAPTER 3. CLASSES apply symm bl
- Page 68 and 69: 68 CHAPTER 3. CLASSES int main() {
- Page 70 and 71: 70 CHAPTER 3. CLASSES class solver
- Page 72 and 73: 72 CHAPTER 3. CLASSES • Compilers
- Page 74 and 75: 74 CHAPTER 3. CLASSES a real number
- Page 76 and 77: 76 CHAPTER 3. CLASSES } return ∗t
- Page 78 and 79: 78 CHAPTER 3. CLASSES This mechanis
- Page 80 and 81: 80 CHAPTER 3. CLASSES One could not
- Page 82 and 83: 82 CHAPTER 3. CLASSES class matrix
- Page 86 and 87: 86 CHAPTER 3. CLASSES of the decrem
- Page 88 and 89: 88 CHAPTER 3. CLASSES There are two
- Page 90 and 91: 90 CHAPTER 4. GENERIC PROGRAMMING }
- Page 92 and 93: 92 CHAPTER 4. GENERIC PROGRAMMING c
- Page 94 and 95: 94 CHAPTER 4. GENERIC PROGRAMMING A
- Page 96 and 97: 96 CHAPTER 4. GENERIC PROGRAMMING v
- Page 98 and 99: 98 CHAPTER 4. GENERIC PROGRAMMING c
- Page 100 and 101: 100 CHAPTER 4. GENERIC PROGRAMMING
- Page 102 and 103: 102 CHAPTER 4. GENERIC PROGRAMMING
- Page 104 and 105: 104 CHAPTER 4. GENERIC PROGRAMMING
- Page 106 and 107: 106 CHAPTER 4. GENERIC PROGRAMMING
- Page 108 and 109: 108 CHAPTER 4. GENERIC PROGRAMMING
- Page 110 and 111: 110 CHAPTER 4. GENERIC PROGRAMMING
- Page 112 and 113: 112 CHAPTER 4. GENERIC PROGRAMMING
- Page 114 and 115: 114 CHAPTER 4. GENERIC PROGRAMMING
- Page 116 and 117: 116 CHAPTER 4. GENERIC PROGRAMMING
- Page 118 and 119: 118 CHAPTER 4. GENERIC PROGRAMMING
- Page 120 and 121: 120 CHAPTER 4. GENERIC PROGRAMMING
- Page 122 and 123: 122 CHAPTER 4. GENERIC PROGRAMMING
- Page 124 and 125: 124 CHAPTER 4. GENERIC PROGRAMMING
- Page 126 and 127: 126 CHAPTER 4. GENERIC PROGRAMMING
- Page 128 and 129: 128 CHAPTER 4. GENERIC PROGRAMMING
- Page 130 and 131: 130 CHAPTER 4. GENERIC PROGRAMMING
- Page 132 and 133: 132 CHAPTER 4. GENERIC PROGRAMMING
3.7. ACCESSING OBJECT MEMBERS 85<br />
};<br />
}<br />
return double index (i1, j.i1);<br />
operator int() const { return i1; }<br />
single index& operator++ () {<br />
++i1; return ∗this;<br />
}<br />
int i1;<br />
This new type overloaded the comma operator so that a second index creates a double index.<br />
The constructor is implicit and the class contains an operator to int. This enables the compiler<br />
to switch between single index and int in both ways.<br />
This allows us to write code like:<br />
or<br />
single index i= 0, j= 1;<br />
std::cout ≪ ”A[0, 1] is ” ≪ A[i, j] ≪ ’\n’;<br />
<strong>for</strong> (single index i= 0; i < A.num rows(); ++i)<br />
<strong>for</strong> (single index j= 0; j < A.num cols(); ++j)<br />
std::cout ≪ ”A[” ≪ i ≪ ”, ” ≪ j ≪ ”] is ” ≪ A[i, j] ≪ ’\n’;<br />
In the loop, an single index (i) is compared with an int (A.num rows()). This comparison operator<br />
is not defined. The compiler converts i implicitly to an int and compares the values as int.<br />
Thus, the conversion operator allows us to use all operations that are defined <strong>for</strong> int without<br />
implementing them.<br />
At this opportunity we can introduce another operator. C and C ++ provide a prefix and postfix<br />
increment/decrement. The difference only manifests if we read the incremented/decremented<br />
value, e.g., j= i++; is differs from j= ++i; by having the old value of i in j (in the first statement)<br />
or the already incremented i (in the second statement). If the increment is the only expression<br />
in the statement, e.g., i++; or ++i;, there is no semantic difference. There<strong>for</strong>e, it does not<br />
matter <strong>for</strong> loops whether we use the postfix or prefix notation.<br />
<strong>for</strong> (single index i= 0; i < A.num rows(); ++i)<br />
is (semantically) equivalent to:<br />
<strong>for</strong> (single index i= 0; i < A.num rows(); i++)<br />
For C ++ integer types it really does not matter. For user-defined types, the compiler will tell<br />
us that this operation is not defined. The GNU Compiler emits the following error message:<br />
no ≫operator++(int)≪ <strong>for</strong> suffix ≫++≪ declared, instead prefix operator tried<br />
Fortunately, it already reveals the solution.<br />
The operator++ without arguments is understood as prefix operator. To define a postfix operator<br />
we must define it with a dummy int argument. This argument has no effect but we need<br />
a way to define the symbol ++ as prefix and postfix operator. Unary operators are defined<br />
as member functions without argument. This works <strong>for</strong> all other unary operators but in case