Saint Louis University |
Computer Science 146
|
Dept. of Math & Computer Science |
Topic: Inheritance Design
Due:
11:59pm Tuesday, 18 April 2012
Please make sure you adhere to the policies on academic integrity.
In class, we outlined a new high-level design for our matrix suite. It makes use of inheritance to avoid the unnecessary repetition of implementation details. Specifically, we rely on the definition of an abstract base class named matrix_expression, which serves as the parent of two concrete classes named matrix and matrix_proxy.
In this assignment, we explore the details of the design and test your intuitions for what behaviors can be easily reused and what behaviors are specific to one class. For example, in the previous assignment we explored various forms of addition. What you hopefully noticed is that doing addition of proxies with each other, or one proxy and one matrix, is really the same logic as when adding two matrices; in all cases we needed to create a new matrix instance, and then to set the elements to be the sum of the respective elements from the two operands. In our new design, we will place this implementation at the level of matrix_expression, so that it can be shared by all. Its formal signature will be described as
matrix matrix_expression::operator+(const matrix_expression& other) const;Each part of that declaration is important in the design. In general, it uses the form:
returnType class::function(params);In this particular example, we note that by defining the behavior in the matrix_expression class, and taking a parameter that is also a matrix_expression, it can be applied to any expression B+C in our hierarchy (e.g., a matrix plus a matrix, a proxy plus a proxy, a proxy plus a matrix). We also note that we have declared the return type to be a matrix instance. So even when adding two proxies, the result is a matrix rather than a proxy. This is because the values in the sum do not exist in any other location before this operation, so we need to create an underlying matrix to store those values.
We want to test your intuitions as you consider many subtle cases that may arise in our new framework. In particular, we want you to consider where and how behaviors are most naturally defined, distinguishing clearly between:
For problems 1 through 6 below, we ask that you describe the most natural place for the behavior, and give a complete formal signature. The signature identifies the class in which the behavior is defined, as well as the type and passing convention for parameters and return values. Make sure to rely on your understanding of the inner-workings of the matrix and matrix_proxy classes, and briefly justify your answer.
For example, if asked to consider the proper support for the syntax A(3,5), we suggest that it is imperative that all matrix expressions support this syntax, but that different implementations will be required for the matrix and matrix_proxy classes. Therefore, we would declare a signature
double matrix_expression::operator()(int r, int c) const;which lies at the topmost matrix_expression class, to guarantee the syntax. But we would need to provide separate implementations of this behavior in the subclasses because the mechanism differs. For a matrix, the location of the desired element is based on directly accessing the data vector assuming column-major ordering of the elements. For a matrix_proxy, there must be a translation between the effective row and column in the proxy, and the actual row and column in the underlying source, given the range of rows and columns being considered.This signature is the "const" form of the operator, in which the caller has only read access to the result, and so the return value is the actual value of the number. We could similarly provide a non-const version with a reference to the number as a return value.
If asked to consider the proper support for the syntax
A + B , we suggest that this behavior should be supported by all matrix expression types, and a single implementation can be shared by all. We suggest the signature
matrix matrix_expression::operator+(const matrix_expression& other) const;We place this behavior in the matrix_expression class, and with a matrix_expression parameter, because we are able to add any two objects that qualify as matrix expressions (e.g.,
matrix + matrix ,matrix + proxy ,proxy + matrix ,proxy + proxy ), so long as the dimensions agree. This is possible because we simply need to compute element-by-element sums, and all matrix expression types support basic (r,c) indexing to access individual elements.The two occurrences of const are there because the evaluation of expression A+B should have no lasting effect on either of the individual operands. Rather, it should generate a new resulting sum.
We have chosen matrix as the return type because we need to create new storage for the result, as the element sums are not explicitly stored in either of the individual operands.
Please give similar analysis for the following syntaxes:
Finally, we want you to carefully consider the role of the assignment operator =, and to determine reasonable semantics depending on the underlying types of the operands. In particular, for each of the following, describe what effect you think the assignment should have and whether there are any restrictions (such as having dimensions agree). You should assume that variables M and N are matrix instances, and variables P and Q are matrix_proxy instances.
Please type your responses, using the numbering scheme above, and submit an electronic version of that document (details on the submission process).