Exercise 5: Vector

Due:   Wednesday, July 22 by 11:59 pm
Rating:   3 (note)
Learning Objectives:

Warm-up Questions

Work through this exercise in the following order:

  1. Read the provided ex5.cc and answer the warm-up questions in questions.txt (in the starter). The questions walk you through discovering which constructors, methods, and operators your class must support, what data members it needs, and what signatures those operations require.
  2. Using your answers, implement Vector.h and Vector.cc.

You will not be graded on the correctness of your answers. They are an opportunity for you to reflect. You may freely discuss the warm-up questions with other students.

Submit your completed questions.txt along with your code.

Problem Description

In this exercise you will write your first C++ class, Vector, a 3-D vector. Writing it brings up most of the design decisions that go into properly encapsulating a class: const-correctness, reference vs. non-reference parameters and return values, constructors, destructors, operator overloading, and the choice between member, non-member, and friend functions.

The array should be allocated when a Vector is created and freed when it is destroyed. The Vector class and all of its associated functions must live in a namespace named vector333.


What to build

The provided ex5.cc shows exactly how your Vector is used, and its expected output (below) shows how each operation should behave. Your first warm-up question is to read ex5.cc and work out the full set of constructors, operators, and other functions your class needs, along with what each one should do.

Note that any operation that returns a Vector must return an actual Vector value, not a pointer to one allocated elsewhere.


Testing your class

We provide ex5.cc, a main that exercises your Vector class, so you do not need to write your own tests. Because the class has no getters, testing is done by printing output rather than by checking return values with status codes (as you did in earlier exercises). Build with the provided Makefile and confirm that your implementation produces exactly the output below:

$ make
$ ./ex5
default Vector, should be (0,0,0): (0,0,0)
Vector with initial values, should be (1,2,3): (1,2,3)
Vector from copy constructor, should be (1,2,3): (1,2,3)
Vector assignment, should have three copies of (3.1,-2.5,2.7):
  (3.1,-2.5,2.7)  (3.1,-2.5,2.7)  (3.1,-2.5,2.7)
Updating assignment, should have two copies of (4.1,-0.5,5.7):
 (4.1,-0.5,5.7) = (4.1,-0.5,5.7)
Updating assignment, should have two copies of (3.1,-2.5,2.7):
 (3.1,-2.5,2.7) = (3.1,-2.5,2.7)
Arithmetic:
  (3.1,-2.5,2.7) + (1,2,3) = (4.1,-0.5,5.7)
  (3.1,-2.5,2.7) - (1,2,3) = (2.1,-4.5,-0.3)
Dot product: (a,b,c) * (x,y,z) = (ax+by+cz)
  (1,2,3) * (1,2,3) = 14
Scalar product: (a,b,c) * k = (ak,bk,ck)
  (3.1,-2.5,2.7) * 2 = (6.2,-5,5.4)
  2 * (3.1,-2.5,2.7) = (6.2,-5,5.4)

Files

  • Vector.h declares the Vector class and its associated functions. You write this.
  • Vector.cc implements the Vector class. You write this.
  • ex5.cc is the test driver. Provided.
  • Makefile builds the ex5 executable. Provided.

The starter Vector.h and Vector.cc contain only the copyright header for you to fill in. See Compilation for how to build.

Friend Functions

Normally, a class's private fields can't be read by functions outside the class. C++ lets you get around this by declaring a function as a friend, which gives that one function permission to read the private members.

A friend is an ordinary non-member function, not a method. You need friends here because a few operations have to read a Vector's components but can't be members: the dot product, scalar multiplication, and stream output.

To write one:

  • Declare it inside the class body, prefixed with the friend keyword.
  • Define it outside the class as a normal non-member function, with no friend keyword and no ClassName:: qualifier.

For example:

// Point.h
class Point {
 public:
  Point(int x, int y) : x_(x), y_(y) { }

  // Grant this non-member function access to Point's private members.
  friend std::ostream& operator<<(std::ostream& out, const Point& p);

 private:
  int x_, y_;
};

// Point.cc -- defined as a normal non-member function (no "friend", no "Point::")
std::ostream& operator<<(std::ostream& out, const Point& p) {
  out << "(" << p.x_ << "," << p.y_ << ")";  // allowed to read private x_, y_
  return out;
}

Only grant friend access to non-member functions that truly need it. A non-member function that can do its job using other public operations should not be a friend. For instance, operator+ can be written using the public operator+=, so it needs no special access and should not be a friend.

You could avoid friends entirely by adding getters, but that would break the encapsulation we want, which is why this exercise leaves them out.

Compilation

We provide a Makefile for this exercise, so you do not need to write one yourself. To build the ex5 executable, just run:

$ make

You can remove the generated executable and object files with:

$ make clean

Under the hood, this compiles your code the same way as the command below (each .cc source file #includes Vector.h):

$ g++ -Wall -g -std=c++17 -o ex5 ex5.cc Vector.cc

Implementation Notes

Getting Started

We recommend referring to the examples from lecture while implementing this exercise. We also suggest you implement operators and test them one at a time. After everything has been implemented, make a style pass and double-check whether each function should be a member, a non-member, or a non-member friend function.

Memory Management

Make sure you use new and delete, as opposed to their C counterparts. Follow the best practices for handling dynamically allocated memory in objects; in particular, investigate all of the functions related to the Rule of Three. Make sure your main has no memory leaks, even for non-typical exits (e.g., handling edge cases or test failures). We will be using valgrind (valgrind --leak-check=full ./ex5) to check for memory issues.

Style Focus

Naming

Our autograder compiles its own tests against your Vector.h, so your class name (Vector) and the namespace (vector333) must match this specification exactly, including capitalization. Every operation must also be callable exactly the way ex5.cc uses it.

Member, non-member, or friend?

Deciding whether each function is a member, a plain non-member, or a non-member friend is one of the subtler parts of C++ class design. Some operations can only be one of these; others could go either way but have a stylistic preference. Grant friend access only to non-member functions that genuinely need to read the class's private members, and review the related lecture material carefully.

Const

Functions, parameters, and variables should be labeled with const appropriately throughout your program. Refer to the lecture material for best practices.

Submission

Submit your work by creating an ex5-submit tag in your exercise repo before the assignment deadline.


Requirements for Full Credit

For full credit, your code must:

  • Be split across the files described above and compile without errors or warnings on CSE Linux machines (lab workstations, attu, or CSE home VM) using the command in the Compilation section.
  • Have no runtime errors, memory leaks, or memory errors (g++ and valgrind).
  • Have a comment at the top of your .cc and .h files with your name(s) and CSE or UW email address(es).
  • Be pretty: the formatting, modularization, variable and function names, commenting, and so on should be consistent with class style guidelines. Additionally, the linter shouldn't have any complaints about your code (cpplint.py).
  • Be robust: your code should deal with hard-to-handle/edge cases and bogus user input (if there are any) gracefully.