C++ for Scientists - Technische Universität Dresden

C++ for Scientists - Technische Universität Dresden C++ for Scientists - Technische Universität Dresden

math.tu.dresden.de
from math.tu.dresden.de More from this publisher
03.12.2012 Views

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

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!