CS 311 Fall 2019 > Midterm Exam Review Problems, Part 1
CS 311 Fall 2019
Midterm Exam Review Problems, Part 1
This is the first of three sets of review problems for the Midterm Exam. For all three sets, see the class web page, or the following links:
Midterm Exam Information
The Midterm Exam will be given in class on Friday, October 11. It will be worth 150 points. The exam will cover all the material of the class from the beginning through Wednesday, October 9.
Referring to books, electronic devices, or other people during the exam will not be permitted.
Limited use of notes will be permitted. Each student may bring to the exam one handwritten sheet of notes (a sheet has two sides) on standard-sized paper (A4, US letter, US legal).
Problems
Review problems are given below. Answers are in the Answers section of this document. Do not turn these in.
- Consider the following C++ code.
Classify each of the following as Lvalue or Rvalue.vector<int> data(10); const int m = 3; data[0] = m; data[m] = 37+data[0];
data
data[0]
m
data[m]
37
37+data[0]
- What is “abstraction” and why do we use it?
- What is an “invariant”?
- Briefly explain the basics of “Operation Contracts” for functions.
(Hint: pre & post.)
- Write reasonable preconditions and postconditions for the following function:
double myFunc(int * p) { p[0] = 5; p[2] = int(std::sqrt(p[1])); return 6.2; }
- What is a “class invariant”?
- How can the use of well chosen class invariants make coding easier?
- In terms of class invariants, what is a constructor for?
- Write reasonable class invariants for the following class,
which maintains an array whose size is stored in the data member
_n
. Hint: The destructor is already written. Write your invariants so that the destructor is correctly written.class MyClass { public: // Ctors, dctor // Default ctor & ctor from int // Pre: // theSize > 0 // Post: // _n == theSize // May throw std::bad_alloc explicit MyClass(int theSize = 10): _p(new int[theSize]), _n(theSize) {} // Dctor // Pre: None. // Post: None. // Does not throw ~MyClass() { delete [] _p; } private: // No copy ctor, copy assn MyClass(const MyClass & other); MyClass & operator=(const MyClass & rhs); public: // General public functions [Various functions go here] private: // Data members int * _p; int _n; };
- List the four ways of passing a parameter (or return value)
in C++.
For each, indicate:
- Whether the method makes a copy of the object passed.
- Whether the method allows passing of const objects.
- Whether the method supports polymorphism & virtual dispatch.
- Whether the method allows implicit type conversions to be performed.
- For each of the following,
explain what it is, and why it is a good thing.
- Loose coupling.
- DRY (principle).
- SRP (principle).
- List the six primary member functions that C++ ompilers
will automatically generate.
For one of the six,
indictate when the compiler will
write it for you.
- For each of the following operators, indicate whether we should
declare it as a member or a global function and how the parameter(s) should be passed
(by value, etc.). Briefly justify your answers.
operator+=
.- Binary
operator+
. operator<<
(for stream output).
- What are the “Big Five”?
- What is the “Rule of Five”?
- In what circumstances do we usually need to write the Big Five?
- If we do not write the Big Five, then who does?
- What is the “Rule of Zero”?
- What does it mean to “own” a resource?
- What does “RAII” stand for, and what is it?
- Give two examples of resources what it means to release each one.
- How can ownership of a resource be shared?
- Suppose you are writing a class
Widget
that has two data members, abool
calledvalid_
and anint *
called_p
. Ifvalid_
istrue
, then_p
points to memory, allocated withnew []
, which the object owns. Ifvalid_
isfalse
, then the value of_p
is unknown. Write the destructor for classWidget
. Include preconditions, if there are any. You may assume that the information above is already in the class invariants.
- In C++ programming we typically access containers, and ranges of data items, through what kind of object?
- What operations are typically defined on the objects from part a?
- In “modern” C++ programming,
how do we usually pass a list of values to a function?
- What is an “iterator”?
- List some things we use iterators for.
- Suppose we have a variables
v1
andv2
, declared as follows:const std::vector<int> v1(SIZE); std::vector<int> v2(SIZE);
- Write a range-based for-loop that prints the items in
v1
, each on a separate line. - Using one of the Standard Template Library algorithms,
write a single function call that
copies the data in
v1
tov2
. Note that they are already the same size.
- Write a range-based for-loop that prints the items in
- What is a “Linked List”?
- Why not simply use an array for everything? Give an advantage of a Linked List, as compared to an array.
- Give a disadvantage of a Linked List, as compared to an array.
- What do we mean by an “error condition” (often just “error”)?
- List three ways of dealing with a possible error condition in a function (not ways of signalling the client code).
- List three ways of signalling client code that an error condition has occurred.
Answers
- Lvalue
- Lvalue
- Lvalue
- Lvalue
- Rvalue
- Rvalue
- Abstraction means separating the purpose and interface of a module from its implementation. Abstraction has a number of advantages, most of which involve the fact that, when we use abstraction, clients typically do not deal with the implementation at all. Thus, the implementation can be changed (bug fixes, etc.) without breaking client code. Furthermore, implementation decisions can be made without consulting everyone involved.
- An invariant is a condition that is always true at a particular point in a program.
- An operation contract for a function is a set of “preconditions” and “postconditions”. A precondition is a statement that must be true when the function is called. A postcondition is a statement that will be true when the function returns (assuming the preconditions were true when it was called). Thus, the operation of a function is described as a contract: the function says to its caller, “I will fulfill my postconditions if you fulfill my preconditions.”
- Preconditions
p
must point to an array of at least 3int
’s.p[1] >= 0
.
p[0] == 5
.p[1]
is unchanged.p[2] == int(sqrt(p[1]))
.- Return value
== 6.2
.
- A class invariant is an invariant that holds for an object of the class outside of public member functions. That is, it is a precondition of all public member functions, except constructors, and it is a postcondition of all public member functions except the destructor. Generally class invariants are statements about a class’s data members that tell what it means for an object to be in a valid (usable) state.
- The existence of class invariants means that public functions can make certain assumptions (usually about class data) when they are called. And it is always easier to code when you know something about the environment your code will be executed in.
- The job of a constructor is to make sure that all class invariants hold for the constructed object.
- Class invariants:
_n > 0
._p
points to memory sufficient to hold_n
int
’s, allocated withnew []
.*this
owns the memory pointed to by_p
.
- The four methods are passing by value, by reference, by reference-to-const, and by Rvalue reference.
Passing by value:
- Makes a copy.
- Allows passing of const objects.
- Does not support polymorphism.
- Allows implicit type conversions.
Passing by reference:
- Does not make a copy.
- Does not allow passing of const objects.
- Supports polymorphism.
- Does not allow implicit type conversions.
Passing by reference-to-const:
- Does not make a copy.
- Allows passing of const objects.
- Supports polymorphism.
- Allows implicit type conversions.
Passing by Rvalue reference:
- Does not make a copy.
- Does not allow passing of const objects.
- Supports polymorphism.
- Allows implicit type conversions.
- Loose coupling means that there is little dependence between modules; we can alter one module without having to alter others. This is good because it makes code less brittle: making a change is unlikely to break the program.
- DRY stands for “Don't Repeat Yourself”. It means that every piece of knowledge is represented just once. This is good because, if something changes, we only need to alter it in one place. It is easy to keep code consistent (and, thus, working).
- SRP stands for “Single Responsilbility Principle”. It states that every module should have one well-defined responsibility. This is good because it makes code clearer, it makes it easier to figure out how code ought to be organized, and it makes error handling easier.
- Silently written member functions:
- Default constructor. Silently written when no constructor is declared.
- Destructor.
- Copy constructor.
- Copy assignment operator.
- Move constructor.
- Move assignment operator.
- We generally want
operator+=
to be a member function. First, we have no reason to make it a global. Second, this operator is tightly tied to its first argument (which it generally modifies), and provides basic functionality (operator+
is usually written to useoperator+=
). Its parameter (the second argument) should generally be passed by reference-to-const. This ensures that it can be called with a const object, avoids making an unnecessary copy, properly handles derived types and virtual function calls, and allows for implicit type conversions, where defined. This operator should generally be something like this:Foo & Foo::operator+=(const Foo & other);
- We generally want binary
operator+
to be a global function, to allow for implicit type conversions on both arguments. Its parameters should generally be passed by reference-to-const, for the same reasons as in part a. This operator should generally be something like this:Foo operator+(const Foo & a, const Foo & b);
- We must declare the stream output operator as a global function;
it cannot be a member, because it would have to be a member of
std::ostream
, and we are not allowed to create new members of this class. Its first parameter (the stream) should be passed by reference, since we modify it. Its second parameter (the value to be output) should be passed by reference-to-const for the same reasons as in part a. This operator should generally be something like this:std::ostream & operator<<(std::ostream & theStream, const Foo & printMe);
- We generally want
- The Big Five are the following member functions of a C++ class: destructor, copy constructor, copy assignment operator, move constructor, move assignment operator.
- The Rule of Five says that if you define one of the Big Five, then you probably need to define or =delete all of them.
- We generally need to write the Big Five when our class directly owns a resource, for example, dynamically allocated memory/objects.
- The Big Five can be silently written by the compiler, if the proper conditions are met.
- The Rule of Zero says, where possible, define none of the Big Five. Resources should be managed by data members that are objects of RAII classes.
- To own a resource means to be responsible for it, in particular, for releasing it.
- RAII stands for “Resource Acquisition is Initialization”. It refers (somewhat misleadingly) to the idea that a resource should be owned by an object, and therefore released by the object’s destructor.
- Dynamically allocated memory.
delete
ing a pointer to it. - An open file. Closing it.
- Dynamically allocated memory.
- The usual way to share resource ownership is by reference counting. A reference count keeps track of the number of owners of a resource. It is incremented when ownership is granted to a new object, and decremented when an object relinquishes ownership. When the reference count reaches zero, the resource is released.
The destructor has preconditions, of course, but they are all class invariants, and so do not need to be given here.// Dctor // Pre: None. ~Widget() { if (valid_) delete [] _p; }
- In C++ programming, we typically access containers using iterators.
- Typically, we define at least the following operations on iterators:
- Assignment.
- Equality/inequality test.
- Increment.
- Dereference.
- We pass a list of values by passing two iterators: one pointing to the first item in the list, and another pointing just past the last item in the list.
- An iterator is an object that acts like a pointer. In particular, it is a pointer-like object that is used to access items in some data set, and which does not own what it points to.
- We use iterators to iterate over the items in a container (thus the name). We also use them to specify ranges of data.
for (auto n : v1) { std::cout << n << std::endl; // Must #include <iostream> }
- We can copy using
std::copy
.std::copy(begin(v1), end(v1), begin(v2)); // Must #include <algorithm>
- A Linked List is a data structure for holding a Sequence. A Linked List consists of a series of nodes. Each holds a single data item and a pointer to the next node. The last node’s next-node pointer is null. There is generally also a head pointer, that points to the first node in the list, or is null if the list is empty.
- A Linked List is much faster at inserting or removing a data item than an array. These operations are constant time for a Linked List, but linear time for an array.
- A Linked List is much slower at doing look-up by index than an array. This operation is linear time for a Linked List, but constant time for an array.
- An error condition is a condition that occurs at runtime; it is something that cannot be handled by the normal flow of execution.
- We can deal with a possible error condition in a function
before, during, of after the function:
- Before. We prevent the error, using a precondition.
- During. We fix the problem inside the function.
- After. We signal the client code that an error condition has occurred.
- We can signal an error in three ways:
- Using a return code.
- Setting a flag that can be checked later.
- Throwing an exception.