labs

c2cpp lab2: MyString Class

This assignment is worth 100 points total.

Please read this assignment carefully and follow the instructions EXACTLY.

Submission

Do NOT create any additional subdirectories. Modify the skeleton code already provided to you in the top level directory.

Ensure the Makefiles build all executables when you run make.

Please refer to the lab submission instruction for other requirements.

Please check your code with valgrind. You will be heavily penalized if you have any memory errors in part 2.

Part 1: Understanding object construction and destruction in C++

The skeleton code contains the same MyString implementation that we learned in class, with the following additions:

Your job is to understand the sequence of basic 4 calls during the execution of test4 program. When you build and run test4 using the Makefile provided, you will see the log output of the basic 4 that looks something like this:

BASIC4TRACE: (0x7f69ac0)->MyString(const char *)    [1] 15,constructor,x
BASIC4TRACE: (0x7f69ad0)->MyString(const char *)    [2] 16,constructor,y
BASIC4TRACE: (0x7f69b00)->MyString(const MyString&) [3]
BASIC4TRACE: (0x7f69af0)->MyString(const MyString&) [4]
BASIC4TRACE: (0x7f69a70)->MyString(const char *)    [5]
BASIC4TRACE: op+(const MyString&, const MyString&)  [6] entering operator+
BASIC4TRACE: (0x7f69a20)->MyString()                [7]
BASIC4TRACE: (0x7f69a80)->MyString(const MyString&) [8] 8,copy constructor, u1 from return temp
BASIC4TRACE: (0x7f69a20)->~MyString()               [9]
BASIC4TRACE: op+(const MyString&, const MyString&)  [10] entering operator+
BASIC4TRACE: (0x7f69a20)->MyString()                [11]
BASIC4TRACE: (0x7f69a90)->MyString(const MyString&) [12]
BASIC4TRACE: (0x7f69a20)->~MyString()               [13]
BASIC4TRACE: (0x7f69b10)->MyString(const MyString&) [14]
BASIC4TRACE: (0x7f69a90)->~MyString()               [15]
BASIC4TRACE: (0x7f69a80)->~MyString()               [16]
BASIC4TRACE: (0x7f69a70)->~MyString()               [17]
BASIC4TRACE: (0x7f69ae0)->MyString(const MyString&) [18]
BASIC4TRACE: (0x7f69b10)->~MyString()               [19]
BASIC4TRACE: (0x7f69af0)->~MyString()               [20]
BASIC4TRACE: (0x7f69b00)->~MyString()               [21]
one and two                                         [22] cout << z << endl;
BASIC4TRACE: (0x7f69ae0)->~MyString()               [23]
BASIC4TRACE: (0x7f69ad0)->~MyString()               [24]
BASIC4TRACE: (0x7f69ac0)->~MyString()               [25]

Answer the following questions in your README.txt:

Part 1a

For each line of the BASIC4TRACE output, write the following information.

As you can see, the answers for a few lines are already filled in for you as examples.

Please prefix your answers with [N], where [N] is the line number in the BASIC4TRACE output, as shown in the example above. You MUST follow this format in order to get credit for this part because it will be auto-graded by a script which will look for [N].

Part 1b

Change the add() function in test4.cpp as follows:

static MyString add(const MyString& a, const MyString& b)

Explain the changes in the BASIC4TRACE output.

Part 1c

The Makefile uses a compiler flag: -fno-elide-constructors. What does this flag do? (See g++ man page.) Rebuild test4 without the flag and examine the BASIC4TRACE output. Describe the changes from (b).

Part 2: Fleshing out MyString class

For part 2, make sure you do valgrind testing. Not having any memory errors will be a big part of the grade.

Part 2a

Implement the following operators for MyString class:

<, >, ==, !=, <=, >=

The comparison should be lexicographical (i.e., what strcmp() does). The == and != operators should evaluate to 1 if the condition is true, 0 if false. (Actually, C++ now has bool type. Feel free to use it if you’d like.)

You’re welcome to implement some of them using the others (for example, you can easily implement != using ==).

Make sure that you can invoke the operators with string literal on either side. That is, both of the following expressions should be valid:

str == "hello"
"hello" == str

where str is a MyString object.

You should NOT overload any of these six operators in order to accept char * arguments. Recall that char * is implicitly promoted to a MyString when necessary.

Write a test driver program, named test5, to test your operators. (And name your source file test5.cpp.) The assert() C library function might be useful for writing a test driver. See the man page for how to use it.

In a C source file, you would #include <assert.h> in order to use assert() function. You have to do things a little differently in C++. You #include <cassert> instead.

Part 2b

Implement += operator that appends a string given on the right-hand side to the one on the left-hand side. For example,

MyString s{"hello"};
s += " world";
cout << s << endl;

will print out “hello world”.

Once you have operator+=(), reimplement operator+() using +=. Using operator+=(), you can implement operator+() without accessing the data members directly. Un-friend operator+().

Do NOT overload these two operators in order to accept char * arguments.

Write some test code that tests your operator+=() and the new version of operator+(). Add your test code to test5.cpp. Your test driver MUST include the following statements:

// test op+=() and op+()

MyString sp{" "};
MyString period{"."};
MyString str;

str += "This" + sp + "should" + sp
    += "work" + sp + "without"
    += sp + "any" + sp + "memory"
    += sp + "leak"
    += period;

cout << str << endl;

You can test more statements if you’d like. Don’t forget the valgrind testing.

Part 3: Move operations

In this part, we explore the move operations – move constructor and move assignment – which was introduced to C++ in the C++11 standard.

Recall from part 1 that, in certain cases, the compiler can optimize your code by eliding certain operations involving temporary objects. Returning a stack-allocated class object by value is a good example. A temporary object is copy-constructed out of the stack object before the stack object gets destructed. Furthermore, the returned temporary object is often copied again to copy-construct yet another object. (This was the case in test4.cpp: MyString z = add(x,y)). Knowing that the temporary object serves no purpose other than to carry the content of the stack object out of the function so that it can become an input to a copy constructor, the compiler can tweak the generated code so that there is no need to create a temporary object.

Unfortunately, there are cases where the compiler cannot apply such optimizations even when we are copying from a temporary object. In many cases, we would have to fully copy-construct a new object out of a temporary object, only to have the temporary object then destroy all the inner data structure that was the source of copying. It would be a lot more efficient if we had a way to transfer or “move” the internals from one object to another, instead of copying it and then destroying the original.

This is the motivation for move constructor and move assignment operations in C++11. The programmer can supply move operations, which will then be used by the compiler, instead of copy operations, when the compiler determines that it’s safe to do so, like copying out of a temporary object which will soon go away, for example. If you provide only copy operations, and no move operations, the compiler will just resort to copy in all cases, unless of course the compiler can optimize them away entirely.

Part 3a

Read 6.2 of “A Tour of C++” by Bjarne Stroustrup.

Part 3b

Implement move constructor and move assignment for MyString class. Make sure that all your test drivers work correctly and valgrind-clean.

First verify your compiler version using the -v option.
For C++11 support that the move operations require, you need at least g++-4.8.1 or clang++-3.3.

The exact C++ standard your compiler defaults to will depend on its version. You can check by consulting the man page for your compiler and looking for the defaulted version in the section that talks about the -std option.

In the provided Makefile, you’ll see that we already set -std=c++14 as a compile flag for you, which is currently a widely used release of the C++ standard.

Part 3c

Repeat the experiment in part 1a with the move operations in place. Be sure to undo the changes from parts 1b and 1c. No need to revert your changes to operator+() from part 2b.

Which copy calls are replaced with move calls? Can you see why those were safely replaced? For the copy calls not replaced with move calls, can you see why they were not replaced?

Good luck!


Last updated: 2023-06-01