CPSC 427: Object-Oriented Programming
Michael J. Fischer
Brackets Example (continued)
Brackets class
Brackets design questions
Is this a good design?
Main file
Storage Management
Objects and storage Objects have several properties:
Name
An object may have one or more names, or none at all!
Not all names are created equal. A name may exist but not be visible in all contexts.
Type of a storage object
Declaration: int n = 123;
This declares an object of type int, name n, and an int-sized storage
block, which will be initialized to 123. It’s lifetime begins when the
declaration is executed and ends on exit from the enclosing block. The
storage class is auto (stack).
The unary operator sizeof returns the storage size (in bytes).
sizeof can take either an expression or a parentheses-enclosed type
name, e.g., sizeof n or sizeof(int).
In case of an expression, the size of the result type is returned, e.g., sizeof (n+2.5) returns 8, which is the size of a double on my machine.
Storage block
Every object is represented by a block of storage in memory.
This memory has an internal machine address, which is not normally
visible to the programmer.
The size of the storage block is determined by the type of the object.
Connecting names to objects
A name can be given to an anonymous object at a later time by using a reference type.
Lifetime
Each object has a lifetime.
The lifetime begins when the object is created or allocated.
The lifetime ends when the object is deleted or deallocated.
Storage class
C++ supports three different storage classes.
Dynamic extensions
Recall that objects have a fixed size determined solely by the object type.
A variable-sized “object” is modeled in C++ by an object with a dynamic extension. This object has a pointer (or reference) to a dynamically allocated object (generally an array) of the desired size.
Example from stack.hpp.
Copying
A source object can be copied to a target object of the same type.
A shallow copy copies each source data member to the corresponding target data member. By default, this is done by performing a byte-wise copy of the source object’s storage block to the target object’s storage block, overwriting its previous contents.
For objects with dynamic extensions, the pointer to the extension gets copied, not the extension itself. This causes the target to end up sharing the extension with the source, and the target’s previous extension becomes inaccessible. This results in aliasing—multiple pointers referring to the same object, which can cause a memory leak.
A deep copy recursively copying the extensions as well.
The double-delete problem
An object with dynamic extension typically uses new in the constructor and delete in the destructor to create and free the object.
When a shallow copy results in two objects sharing the same extension, then attempts will be made to delete the extension when each of the two copies of the object are deleted or go out of scope.
The first delete will succeed; the second will fail since the same object cannot be deleted twice.
This is called the double delete problem and is a major source of memory management errors in C++.
Takeaway: Don’t copy objects with dynamic extensions.
When does copying occur?
C++ has two operators defined by default that make copies:
The symbol = means assignment when used in a statement, and it
invokes the copy constructor when used in an initializer. All-by-value
argument passing also uses the copy constructor.
Assignment modifies an existing object;
The copy constructor initializes a newly-allocated object.
Assignment
The assignment operator = is implicitly defined for all types. The assignment b=a modifies an already-existing object b as follows:
Copy constructor
The copy constructor is implicitly defined for all types. Like any constructor, it can be used to initialize a newly-allocated object.
Since the copy constructor uses shallow copy, any use of it on an object
with dynamic extension leads to the double delete problem.
If you don’t intend to use the copy constructor, you can disable it by writing T(const T&) =delete; in class T’s definition.
Redefining assignment and the copy constructor
You can redefine assignment for a class T by defining the function with
signature T& operator=(const T&);.
You can redefine the copy constructor by defining the function with
signature T(const T&).
To get the implicit definitions (if they’ve been deleted and you want them), use =default. To cancel them, use =delete.