Author : Ian Joyner
Page : << Previous 6 Next >>
twice in a class, it is a syntax error.
Inheritance introduces some questions over and above this simple
consideration of scope. Should a name declared in a base class be in
scope in a derived class? There are three choices:
1) Names are in scope only in the immediate class but not in
subclasses. Subclasses can freely reuse names because there is no
potential for a clash. This precludes software reusability. Subclasses
will not inherit definitions of implementation. Therefore case 1 is not
worth considering.
2) The name is in scope in a subclass, but the name can be overloaded
without restriction. This is closest to the overloading of names in
nested blocks. This is C++'s approach. Two problems arise. Firstly,
the name can be unintentionally reused. Secondly, because the new
entity is not assumed to have any relationship to the original, its
signature cannot be type checked with the original entity. Since
consistency checks between the superclass and subclass are not possible,
the tight relationship that inheritance implies, which is fundamental to
object-oriented design, is not guaranteed. This can lead to
inconsistencies between the abstract definition of a base class, and the
implementation of a derived class. If the derived class does not
conform to the base class in this way, it should be questioned why the
derived class is inheriting from the base class in the first place.
(See the nature of inheritance.)
3) The name is in scope in the subclass, but can only be overloaded
in a disciplined way to provide a specialisation of the original. Other
uses of the name are reported as duplicate name errors. This form of
overloading in a subclass ensures the entity referred to in the subclass
is closely related to the entity in the ancestor class. This helps
ensure design consistency. The relationship of name scope is not
symmetric. Names in a subclass are not in scope in a superclass
(although this is not the case in typeless languages such as Smalltalk).
In order to provide the consistent customisation of reusable software
components, the same name should only be used by explicitly redefining
the original entity. The programmer of the descendant class should
indicate that this is not a syntax error due to a duplicate name, but
that redefinition is intended. (This has already been covered in the
virtual section.) This choice ensures that the resultant class is
logically constructed. This might seem restrictive, but is analogous to
strong typing, and makes inheritance a much more powerful concept.
3.8. '.' and '->'
The '.' and '->' member access syntax came from C structures. It
illustrates where the C base adversely affects flexibility.
Semantically both access a member of an object. They are, however,
operationally defined in terms of how they work. The dot ('.') syntax
accesses a member in an object directly. For example 'x.y' means access
the member y in the object x.
obj x; // declare object x of class obj with a member y.
x.y; // access y in object x directly
x->y; // syntax error ". expected"
The '->' syntax means access a member in an object referenced by a
pointer. For example 'x->y' (or the equivalent *(x).y) means access the
member y in the object pointer x refers to .
obj *x; // declare a pointer x to an object of class obj.
x->y; // access y via pointer x
x.y; // syntax error "-> expected"
In this example, 'what' is to be computed is "access the element y of
object x." In C++, however, the programmer must specify for every access
the trivial detail of 'how' this is done. The compiler can easily
remove this burden from the programmer, as in fact most languages do.
Furthermore, this reduces flexibility as if the 'obj x' declaration is
changed to 'obj *x', the effect is widespread as all 'x.y' must be
changed to 'x->y'. Since the compiler gives a syntax error if the wrong
access is used, this shows it already knows what access code is required
and can generate it automatically. Good programming centralises
decisions. The decision to access the object directly or via a pointer
should be centralised in the declaration.
3.9. Anonymous parameters in Class Definitions
C++ does not require parameters in function templates to be named.
The type alone can be specified. For example a function f in a class
header can be declared as f (int, int, char). This gives the client no
clue to the purpose of the parameters, without referring to the
implementation of the function. Meaningful identifiers are essential in
this situation, because this is the abstract definition of a routine. A
client of the class and routine must know that the first int represents
a 'count of apples', etc. It is true that well known routines might not
require a name, for example sqrt (int). But this is not appropriate for
large scale software development. The use of anonymous parameters
handicaps the purpose of abstract descriptions of classes and members:
to facilitate the reusability of software. Program text captures the
meaning of the system for some future activity, such as extension or
maintenance. To achieve reusability, communication of intent of a
software element is essential. A compiler strips away this level of
communication, producing a machine executable entity. Languages and
compilers that perform less than optimal translations should not
penalise careful production of semantic entities. But neither should a
language definition allow less than optimal expression to the human
reader. Languages do not have to be cryptic to achieve efficiency. In
fact cryptic languages impair efficiency, as they make it harder for the
programmer to develop efficient systems, and furthermore, they make it
harder for automatic code optimisers.
Names are not strictly necessary in programming. Naming exists to
help the human reader identify different entities within the program,
and to reason about their function. For this reason naming is
essential. Without naming, development of sophisticated systems would
be nearly impossible. Some languages access parameters by their address
(position) in the parameter list ($1, $2, etc). This is quite
unsatisfactory, even for shell scripts. Anonymous parameters can save
typing in a function template, but then programming is not a matter of
convenience. This is inconvenient for later readers. The redundancy is
beneficial and saves later programmers having to look up the information
in another place. A real convenience in function templates would be
that abstract function templates be automatically generated from the
implementation text (see header files for more details).
Anonymous parameters illustrate the link between courtesy and
safety issues in programming. Due to pressure of work, a client
programmer might wrongly guess the purpose of a parameter from the
type. Thus the failure of the original programmer to provide a
courtesy has caused a later programmer to breach safety. An interface
client must know the intention of the interface for it to be
used effectively.
3.10. Nameless Constructors
Multiple constructors can have different signatures, similar to
overloaded functions. This precludes two or more constructors having
the same signature. Constructors are also not named (apart from the
same name as the class). This makes it difficult to discern from the
class header the purpose of the different constructors. It is difficult
to match an object creation with the called constructor. Constructors
suffer from all of the problems described with regards to functions with
the same name but different signatures. It would be easy to mark
routines as constructors, for example:
constructor make (...)...
constructor clone (...)...
constructor initialise (...)...
where each constructor leaves the object in valid, but potentially
different states. Named constructors would aid comprehension as to what
the constructor is used for in the same way as function names document
the purpose of a function. Secondly, named constructors would allow
multiple constructors with the same signature. Thirdly, it is easier to
match up an object creation with the constructor actually called.
3.11. Constructors and Temporaries
A 'return <expression>' can result in a different value than the
result of <expression>. In section 6.6.3, the C++ ARM says "If required
the expression is converted, as in an initialisation, to the return type
of the function in which it appears. This may involve the construction
and copy of a temporary object (S12.2)."
Section 12.2 explains "In some circumstances it may be necessary or
convenient for the compiler to generate a temporary object. Such
introduction of temporaries is implementation dependent. When a
compiler introduces a temporary object of a class that has a constructor
it must ensure that a constructor is called for the temporary object."
A note says "The implementation's use of temporaries can be observed,
therefore, through the side effects produced by constructors and
destructors."
Putting this together,
Page : << Previous 6 Next >>