Overview
- Motherhood and apple pie
- the design and philosophy of C++
- advice for programmers from other languages (in particular Java)
- some history (if you're interested)
Topics
Philosophy and goals
- C++ is a general-purpose programming language with a bias toward systems programming
- C++ is a general-purpose programming language providing a direct and efficient model of hardware combined with
facilities for defining lightweight abstractions
- Leave no room for a lower-level language below C++
- What you don't use, you don't pay for
C++ Programming styles
- Procedural programming
- Procedure-centric
- Program consists of procedures (functions) calling each other
- Procedural decomposition
- Derives from C
- Data abstraction (modules — interface vs implementation, classes, encapsulation, access control)
- hiding of internals and implementation details
- classes do this
- so do modules
- Object-oriented programming (inheritance and polymorphism)
- Generic programming (templates)
- algorithms and structures written in as type-agnostic (parameter and element types) manner as possible
- generic algorithms: search, sort
- generic structures: containers
- Class-oriented
- user-defined types focus on the notion of a class
Type-checking
- static vs dynamic
- Python: dynamically-typed
> x = "Hello"
> print(x) # Hello
> x = 3
> int(x) # 3
> y = x + "Hello" # error
- Java (and C++) polymorphism requires dynamic type information and checking
…
Super sup;
System.out.println("Enter a number: ");
int n = scanner.nextInt();
if (n % 2 = 0)
sup = new Super();
else
sup = new Sub1();
Sub1 sub1 = (Sub1)sup; // will this work?
Abstraction
- process of generalization to allow for a wider application space
- focus on relevant details and ignore irrelevant one
- the abstracted result is often called a model
- applicable to both functions (behavior) as well as data (state)
- over-abstraction
- abstracting beyond a reasonable amount for the application
Programming in C++
- [1] Represent ideas directly in code.
- Better to make something part of the code than a comment
int s; // speed in mph
…
if (s > 55) … // over limit
as opposed to
int speed;, // speed in mph
…
if (speed > SPEED_LIMIT) … // over limit
- even better would be to introduce a
Speed
class that would have all this logic built-in as well as preventing the
assignment of a speed greater than SPEED_LIMIT
- [2] Represent relationships among ideas directly in code (e.g., hierarchical, parametric, and ownership relationships).
- Corollary of 1.1
- super/subclass; element type for a generic; user-defined type for combining components (e.g., array + tos for stack)
- the alternative is to not have the relationship be specified in the actual code, but described in comments
- [3] Represent independent ideas independently in code.
- Keep separate tasks separate in the code
- [4] Keep simple things simple (without making complex things impossible).
More specifically:
- [5] Prefer statically type-checked solutions (when applicable).
- better to catch things at compile-time than at run-time
- [6] Keep information local (e.g., avoid global variables, minimize the use of pointers).
- difficult to keep track of global info in a large system
- also information about global object might not be in current file/context
- also, recall the advantages of local objects in terms of object lifetime and resource management
- [7] Don't overabstract (i.e., don't generalize, introduce class hierarchies, or parameterize beyond obvious needs and
experience).
Suggestions for Java Programmers
Many of these issues are not relevant in Java, and thus they are directed specifically to Java programmers who may not be aware of the
issues involved. We will (hopefully) revisit this section once we've covered the corresponding C++:
- [1] Don't simply mimic Java style in C++; that is often seriously suboptimal for both maintainability and performance.
- C++ is not Java, trying to mimic Java in C++ is like trying to put a square peg into a round hole
- [2] Use the C++ abstraction mechanisms (e.g., classes and templates): don't fall back to a C style of programming out
of a false feeling of familiarity.
- Meant for Java programmers who came from C?
- [3] Use the C++ standard library as a teacher of new techniques and programming styles.
- The C++ library was written and is maintained by those who are most familiar with the language, its style and conventions
- [4] Don't immediately invent a unique base for all of your classes (an Object class). Typically, you can do better
without it for many/most classes.
- Doing this is a result of missing (or feeling lost without) the Java class hierarchy
- C++ has its own way of doing many things without resorting to inheritance and hierarchy
- [5] Minimize the use of reference and pointer variables: use local and member variables (§3.2.1.2, §5.2, §16.3.4, §17.1).
- [6] Remember: a variable is never implicitly a reference.
- [7] Think of pointers as C++'s equivalent to Java references (C++ references are more limited; there is no
reseating of C++ references).
- We will go into this in more depth when we cover pointers and references
- [8] A function is not virtual by default. Not every class is meant for inheritance.
- A virtual function in C++ is one that can be overridden.
- This is a consequence of virtual (i.e., overridden, polymorphic) functions requiring runtime support, and C++'s design philosophy
of "don't pay for what you don't use".
- [9] Use abstract classes as interfaces to class hierarchies; avoid "brittle base classes," that is, base classes
with data members.
- C++ has no equivalent of Java's interface construct — other than an abstract class where all functions are abstract
- Having (accessible) data members (instance variables) in a base (super) class means that changes, additions, deletions, etc to those
data member can break the derived (sub) classes.
- [10] Use scoped resource management ("Resource Acquisition Is Initialization"; RAII) whenever possible.
- This is a restatement of sorts of
[1.5]
- 'scoped resource management' means using the scoping rules of the language to do as much of the memory management work for you as possible
- using local variables — they go in and out of scope automatically, and their lifetime is controlled in the same fashion
- similarly for data members — they are created and destroyed in tandem with their enclosing object (instance)
- In Java, the destruction is not much of an issue, but in C++, we will have the deletion of the object occur within
a destructor
- More on RAII
- [11] Use a constructor to establish a class invariant (and throw an exception if it can't).
- An invariant is a property that must hold true. A class invariant
is an invariant dealing with i(constraining) the objects of a class.
- The constructor is invoked when the object is created; that is a perfect time to make sure the object is properly
initialized and conforms to any restrictions or rules (i.e., invariants) for the class's instances
Rational
had such an invariant — no 0
denominator; also the
normalization of the number, i.e., all objects of the Rational
class must
have their values in normal form.
- [12] If a cleanup action is needed when an object is deleted (e.g., goes out of scope), use a destructor for that.
Don't imitate finally (doing so is more ad-hoc and in the longer run far more work than relying on destructors).
- [13] Avoid "naked" new and delete; instead, use containers (e.g., vector, string, and map) and handle
classes (e.g., lock and unique_ptr).
- This is related to
[1.10]
; placing new
and delete
inside the member functions of well-defined class
brings it under the umbrella of 'scoped management'.
- [14] Use freestanding functions (nonmember functions) to minimize coupling (e.g., see the standard
algorithms), and use namespaces (§2.4.2, Chapter 14) to limit the scope of freestanding functions.
- freestanding functions — non-member, C-style, file-scope
- implementing a generic algorithm as a freestanding function makes it available to all; making it a member
function limits it to that class (and its descendants)
- C++'s template facility provides generic programming in a non-hierarchical context
- As an example, a freestanding sort function (implemented generically as a template) can be called for ANY type; OTOH if it's implemented at
the base of a class tree, it can only be called for element types that are descendants of that tree.
- [15] Don't use exception specifications (except noexcept; §13.5.1.1).
- [16] A C++ nested class does not have access to an object of the enclosing class.
- This is in contrast to Java's inner classes which do have such access
- [17] C++ offers only the most minimal run-time reflection: dynamic_cast and typeid (Chapter 22).
Advice
- [1] Represent ideas (concepts) directly in code, for example, as a function, a class, or an enumeration; §1.2. [CG P.1]
- Using a meaningfully-named variable places the semantics of the variable into the code.
- Using a meaningfully-named constant removes magic numbers from your code and replaces it with semantically useful information
- Introducing meaningful type aliases provides additional meaning to your declarations
- Introducing functions (with meaningful names) provides more meaning and readability than simply having the code inline.
- Introducing new types via classes provides enhanced functionality and semantics to your program.
- [2] Aim for your code to be both elegant and efficient; §1.2.
- Elegance requires a solid knowledge of the language's features and idioms
- Efficiency requires the same, but also a proper understanding of the underlying algorithm and its
implementation within the language
- [3] Don't overabstract; §1.2.
- If you already have all the abstraction of our illustration above, that's fine; but if you don't, it makes no sense to start
implementing generics or lambdas simply to print an array.
- [4] Focus design on the provision of elegant and efficient abstractions, possibly presented as libraries; §1.2.
- Expend effort on the 'big things'.
- [5] Represent relationships among ideas directly in code, for example, through parameterization or a class hierarchy; §1.2.1.
- class hierarchies mean inheritance which represents an is-a relationship
- parameterization (think of the generic parameter of a collection) allows one to abstract out the common qualities of the container
- [6] Represent independent ideas separately in code, for example, avoid mutual dependencies among classes; §1.2.1.
- Responsibility-driven code; don't design class that assume multiple roles/responsibilities (e.g., I/O classes …
separate the notion of a byte source from the reading of the bytes, and then from the scanning/parsing of the tokens)
- Same line of thought …
add
and addInPlace
… one of the methods should handle the semantic of rational
addition, the other the appropriate memory management required for using the other.
- Ignoring this piece of advice means that even if you only need the functionality of one class, both must be brought into your code
- [7] C++ is not just object-oriented; §1.2.1.
- object-oriented programming is defined as a style of programming that uses encapsulation, inheritance, and polymorphism
- An object-oriented language is one in which these concepts are central to the programming methodology used.
- C++ is said to support object-oriented programming, rather being an object-oriented language.
- C++ provides the above facilities but they are not the central focus of the language
- [8] C++ is not just for generic programming; §1.2.1.
- Generic programming allows for the specification of an algorithm, deferring specific types to a later time; it is manifested in C++
through the use of templates
- But there are many other programming styles in C++; chose the one (or several) that's most appropriate for the task.
- [9] Prefer solutions that can be statically checked; §1.2.1.
- Don't want errors cropping up at run-time, when they could have been caught at compile-time.
- [10] Make resources explicit (represent them as class objects); §1.2.1, §1.4.2.1.
- A resource is usually defined as anything that is used by the program with a limited capacity or availability
- Memory, for example, is a resource
- So are files
- Don't scatter resource management throughout your code in an ad-hoc fashion
- This is another sales pitch for RAII
- [11] Express simple ideas simply; §1.2.1.
- [12] Use libraries, especially the standard library, rather than trying to build everything from scratch; §1.2.1.
- They've been tested, and they're probably better written than anything you or I would write.
- [13] Use a type-rich style of programming; §1.2.2.
- C++ provides a variety of mechanisms for providing semantically meaningful aliases for existing types as well
as the introduction of new types.
- Using such aliases and semantically meaningful types makes the code vastly more readable, and will often
provide additional type security.
- [14] Low-level code is not necessarily efficient; don't avoid classes, templates, and standard-library components out of fear of performance problems; §1.2.4, §1.3.3.
- Optimizers today typically generate better code than a programmer, especially at the larger scale
- Don't get clever with very low-level code; the optimizer can often do better the higher leave the code
- [15] If data has an invariant, encapsulate it; §1.3.2.
- Introducing a class (i.e., encapsulating the data) also introduces member functions, and once you have functions you can enforce
invariants (rules or conditions) on the data.
- [16] C++ is not just C with a few extensions; §1.3.3.