The new operator allocates storage on the heap.
This storage is not deallocated unless explicitly deallocated with the delete operator.
The new operator sytax is roughly one of:
new type_expression new type_expression( parameter_list ) new const type_expression new const type_expression( parameter_list )
The value of a new operator expression is the address of the storage allocated by new and the type is pointer to type_expression.
Some simple examples using primitive types:
int *pi = new int; int *qi = new int(5); const int *pci = new const int(10); pci = qi; // OK pci = pi; // OK pi = pci; // NOT Allowed!
Storage allocated with the new operator is deallocated with the delete operator.
The operand of the delete operator is a pointer value that should be the address of storage previously allocated with new.
It is a drastic error to use delete on a pointer value that points to a local variable allocated on the stack or to a global variable.
The syntax is
delete pointer__expression
If the pointer_expression has value 0 (null pointer), the operator has no effect.
The delete operator has no return value (as a function its return type is void).
The new [] operator allocates storage for an array on the heap.
This storage is not deallocated unless explicitly deallocated with the delete [] operator.
The new [] operator sytax is roughly one of:
new type_expression[integer_expression] new type_expression[integer_expression][constant_integer]
If more subscripts are used, all except the first one must specify a constant integer size.
The value of a new [] operator expression is the address of the first element of the array storage allocated by new [] and the type is pointer to type_expression.
Note: the return type of new int and new int[n] are the same: pointer to int.
Array storage allocated with the new [] operator is deallocated with the delete [] operator.
The operand of the delete [] operator is a pointer value that should be the address of array storage previously allocated with new [].
As with the sinlge object delete operator, It is a drastic error to use delete [] on a pointer value that points to a local variable allocated on the stack or to a global variable.
The syntax is
delete [] pointer__expression
If the pointer_expression has value 0 (null pointer), the operator has no effect.
The delete [] operator also has no return value (as a function its return type is void).
If no parameter list is used with the single version of new for primitive types, the allocated storage is not initialized.
int * p = new int; // *p not initialized int * q = new int(5); // *q initialized to 5
For class types, an appropriate constructor is invoked as usual with the single version of the new operator:
Employee *ep = new Employee; Employee *eq = new Employee("Bob"); // *ep initialized with Employee() constructor // *eq initialized with Employee(const string&) constructor
No initialization parameter list is allowed with the new [] operator.
For arrays of primitive types, the array elements are not initialized.
For arrays of class types, that class type must have an effective no argument constructor and that constructor is used to initialize each element.
/** * p[0], p[1], ..., p[9] not initialized */ int *p = new int[10]; /** * ep[0], ep[1], ..., ep[9] each initialized * with the no-arg Employee() */ Employee *ep = new Employee[10];
If storage was allocated for a class type with the (single) new operator, it should be deleted with the delete operator and the destructor for the class (if there is one) will execute just before the storage is deallocated..
Employee *ep = new Employee("Bob"); .... delete ep; // ~Employee() is called for Bob
If storage was allocated for an array of class type elements using the new [] operator, it should be deleted with the delete [] operator and the destructor for the class (if there is one) will execute for each of the array elements.
Employee *ep = new Employee[5]; .... delete [] ep; // ~Employee() executes once for each // of ep[0], ep[1], ... ep[4]
No compiler error results if new [] is used to allocate an array on the heap, but delete is used to deallocate instead of delete []. Why?
Because both operators, new and new [], return the same type:
Employee *p = new Employee; Employee *q = new Employee[5];
Deletion should be:
delete p; delete [] q;
If the delete operator is accidentally used to delete the array instead of delete []
delete q;
then
Compiler errors will occur if const is used inconsistently in defining properties of method parameters and/or their effect on instance members.
Advice:
For class object parameters that a method will not modify use pass by constant reference instead of pass by value.
Only use pass by reference if the method will actually need to change the actual parameter (in the calling function).
For primitive type parameters that a method will not change, use pass by value.
For parameters of pointer type passed to methods that will not modify the value pointed to, use const; that is, declare the parameter to be a pointer to a const type. E.g.,
strpy(char *to, const char *from)
the second parameter string will not be changed by strcpy. So delcare that parameter to be a pointer to a constant char. Then the characters in that string cannot be changed by strcpy.
The compiler will then check that the method indeed doesn't make any changes. But it will also know an instance is passed by constant reference to some function, that this method can still be used because it is guaranteed not to make any changes.
int getX() const { return x; } int get(int index) const { return arr[index]; } string getName(const string& ssNum) const { // search array for soc sec number matching ssNUm // return corresponding name. // The parameter ssNum will not be changed (first const) // But also this method will not change any data // member of the class either. (second const) }
Only if a method will potentially modify one or more data members should the const after the parameter list be omitted.
However, do not use const after the parameter list for static methods whether they modify static members or not.
Static methods can't modify instance members! (Why?)
Suppose a class Employee has a static method, void setId(int newId); (at line 10) that changes a static data member, id.
The static setId method is not marked const.
Employee also has two "get" methods at lines 8 and 9 that do not change any data members; these "get" methods are markded const.
1 class Employee 2 { 3 static int id; 4 int myid; 5 string name; 6 public: 7 ... 8 int getId() const; 9 string getName() const; 10 static setId(int newId); 11 ... 12 };
This print function with constant Employee reference parameter, is correct and generates no compile errors even though the setId method at line 3 changes one of the static members of the Employee class.:
1 void print(const A& a) 2 { 3 a.setId(20); 4 cout << "\nA object:" << endl; 5 cout << "Id: " << a.getId() << endl; 6 cout << "x: " << a.getX() << endl; 7 8 }
Rule of three: If a class has a pointer member, the class needs:
The example used was an Employee class that used C-string members, one of which was dynamically created with the new[] operator.
x Here is the Employee class again, but with const inserted according to the guidelines
1 class Employee 2 { 3 char *name; 4 char ssn[12]; 5 double salary; 6 public: 7 Employee(); 8 Employee(const char nm[], const char ss[], double sal); 9 Employee(const Employee& e1) 1. Copy constructor! 10 { 11 int len = strlen(e1.name); 12 name = new char[len + 1]; 13 strcpy(name, e1.name); 14 memset(ssn, '\0', 12); 15 strncpy(ssn, e1.ssn, 11); 16 salary = e1.salary; 17 } 18 19 void operator=(const Employee& e1) 2. Assignemnt operator 20 { 21 delete [] name; 22 int len = strlen(e1.name); 23 name = new char[len + 1]; 24 strcpy(name, e1.name); 25 memset(ssn, '\0', 12); 26 strncpy(ssn, e1.ssn, 11); 27 salary = e1.salary; 28 } 29 const char * getName() const; 30 const char * getSSN() const; 31 double getSalary() const; 32 void setSalary(double sal); 33 34 virtual ~Employee() 3. Destructor 35 { 36 delete [] name; 37 } 38 39 };
Overloading operator<< for a class (such as Employee) is rather different than overloading operator=.
Why?
Employee e1, e2("Bob", "123-45-6789"); e1 = e2; // same as e1.operator=(e2);
The form e1.operator=(e2) emphasizes that this operator= is a member of the Employee class (since e1 is of type Employee).
What about writing an overloaded operator<< for the Employee class?
cout << e1; // same as cout.operator<<(e1) ???
If the form were cout.operator<<(e1) then the overloaded operator<< would have to be written as a member of the class that cout belongs to - ostream!
Uh oh! The ostream class doesn't belong to us!
We can't define new members of the ostream class.
And we can't make operator<< be a member of the Employee class.
But we can define a non-member overloaded operator<<
ostream& operator<<(ostream& os, const Employee& e);
ostream& operator<<(ostream& os, const Employee& e);
The return type must be an ostream in order to write:
cout << e << endl;
because this will be equivalent to
operator<<(cout, e) << endl;
From this form it is clear that the type of the return value of operator<< must be acceptable as the left operand of <<.
That is, the return value must be of type ostream.
Since operator<< for Employee must be a non-member function, it will not have direct access to the private data members!
1 ostream& operator<<(ostream& os, const Employee& e) 2 { 3 os << "Name: " << e.getName() << endl; 4 os << "SSN: " << e.getSSN() << endl; 5 return os; 6 }
Using the public interface of Employee in writing operator<< works, but there are more flexible choices.
ostream& operator<<(ostream& os, const Employee& e) { os << e.toString(); return os; }
Declaring a function to be a friend function of Employee means the function can access all members including private members of any Employee parameter passed to it.
The first choice has superior benefits for opertor<< in case subclasses of Employee are created later (such as a Manager subclass).
On the other hand "friend" functions have benefits when overloading operators such as operator+ for a class A, as we'll see later.
Do we need to provide an additional operator<< for files?
That is, do we need both of these:
1. ostream& operator<<(ostream& os, const Employee& e); 2. ofstream& operator<<(ofstream& os, const Employee& e);
The ofstream class inherits from ostream.
So an ofstream can be passed as the first parameter to version 1 and version 2 is not needed.
Used in EmployeeApp.cpp: #include "Employee.h" int main() { ofstream ofs; ofs.open("output.txt"); if ( ofs.fail() ) { cout << "Unable to open output file 'output.txt'\n"; exit(1); } Employee e("Bob", "123-45-6789"); ofs << e << endl; ofs.close(); return 0; }
The overloaded operator<< for Employee is declared in Employee.h and defined in Employee.cpp.
Defined in Employee.cpp #include "Employee.h" ostream operator<<(ostream& os, const Employee& e) { os << e.toString(); return os; }
If a Manager class is created as a subclass of Employee, do we need to write another operator<<?
That is, do we need both of these:
1. ostream operator<<(ostream& os, const Employee& e) 2. ostream operator<<(ostream& os, const Manager& m)
Answer: No, provided some conditions are met:
Defined in Employee.cpp #include "Employee.h" ostream operator<<(ostream& os, const Employee& e) { os << e.toString(); return os; }
Then a Manager instance can be passed to the operator<< already written for Employee and the e.toString() will use Manager's implementation of toString().
What is needed to write a toString() method for a class?
Answer: We will usually want to provide a string representation that includes all the data member values of the instance.
So the more general problem is how to convert a collection of different data type values to a single string!
There is a C style way and a C++ style way to convert a collection of data values to a single string.
Each way uses the input/output facilities of the language, but with the output going (ultimately) to a string rather than to standard output or a file!
The C++ style will use the C++ string type and a new type: stringstream.
The stringstream type is similar to ostream and istream in that it allows both << and >> operators.
In fact stringstream inherits all the methods and operators from istream and ostream.
The << operator allows the different types to be "inserted" into the stringstream just as for ostream.
Just as for ostream, values inserted into a stringstream are converted to a string!
However, the stringstream object has a string buffer where it stores this string rather than sending the string to standard output.
There are three stringstream methods that manage the string buffer of a stringstream:
To use the stringstream type, a header file similar to <ostream> and <fstream> is needed:
#include <ostream> // o for output stream #include <istream> // i for input stream #include <fstream> // f for file streams #include <sstream> // s for string streams
Example. Convert name, ss, id, and amt to a single string.
string name = "Bob"; string ss = "123-45-6789"; int id = 175; double amt = 55000.00; stringstream ss; ss.setf(ios::fixed); ss.precision(2); ss << "Name: " << name << endl << "SSN: " << ss << endl << "Id: " << id << endl << "Amount: << amt; string result = ss.str();
Finally, consider the class Pair:
1 class Pair 2 { 3 int x; 4 int y; 5 public: 6 Pair() : x(0), y(0) 7 { } 8 9 Pair(int x, int y) : x(x), y(y) 10 { } 11 12 int getX() const; 13 int getY() const; 14 virtual string toString() const; 15 16 friend ostream& operator<<(ostream& os, const Pair& pr); 17 18 };
We really don't need to make this operator<< be a friend, since it will use the public method toString().
A friend function may be declared inside a class definition, even though it is not a member function of the class.
If a non-member function is not a friend, it must be declared outside the class.
If operator<< is not declared a friend in the Pair class, then in the Pair.h file, it should be declared outside the Pair class:
1 class Pair 2 { 3 int x; 4 int y; 5 public: 6 Pair() : x(0), y(0) 7 { } 8 9 Pair(int x, int y) : x(x), y(y) 10 { } 11 12 int getX() const; 13 int getY() const; 14 virtual string toString() const; 15 16 17 }; 18 19 ostream& operator<<(ostream& os, const Pair& pr);
The implementation of Pair's toString() method
1 string Pair::toString() const 2 { 3 stringstream ss; 4 ss << x << " " << y; 5 return ss.str(); 6 }
The usual arithmetic operators can be overloaded for types provided there is not already a definition of the operator for that type.
So operator+ cannot be overloaded for the int type, but can be overloaded for the example Pair type.
There are two choices for overloading an operator for a class type:
In case of a non-member, the function can either be a friend or not, depending on whether you need or want to access private members of instances.
Suppose we want to overload operator+ for the Pair class.
Pair p1(1,2); Pair p2(5,5); Pair p; p = p1 + p2; p should be the pair with x=6, y=7
If we make this operator a member function of Pair, then p1 + p2 will be equivalent to calling the operator+ method on p1 with argument p2; (so this operator+ will have only 1 parameter):
p = p1.operator+(p2);
Note that changing the order of the operands of + should not change the result, but the roles of p1 and p2 change:
p = p2 + p1;
is equivalent to
p = p2.operator+(p1); // Ok p2 and p1 are both Pair's
1 Pair Pair::operator+(const Pair& other) const 2 { 3 Pair result(*this); 4 result.x += other.x; 5 result.y += other.y; 6 return result; 7 }
Suppose we want to multiply each element of Pair by an int:
Pair p1(3,5); Pair p; p = 2 * p1; or p = p1 * 2; // p should now be the pair x=6, y=10
The overloaded operator* will have one operand of type int and the other of type Pair.
Should this operator* be a member function or not?
It can't be a member function, because the expression 2*p1 would mean
p = 2 * p1;
would mean
p = 2.operator*(p1); // 2 is not of class type
So we would need to make operator* be a non-member function. (It will have 2 parameters.)
We actually need two operator* functions, one for each order of the two operands of *:
class Pair { int x; int y; public: Pair(); Pair(int x, int y); int getX() const; int getY() const; virtual string toString() const; Pair operator+(const Pair& b) const; friend Pair operator*(int a, const Pair& p); friend Pair operator*(const Pair& p, int a); friend ostream& operator<<(ostream& os, const Pair& pr); };
The implementaions (in Pair.cpp).
Note: The Pair:: class scope operator is not used since these are not member functions of Pair:
Pair operator*(int a, const Pair& p) { return Pair( a * p.getX(), a * p.getY() ); } Pair operator*(const Pair& p, int a) { return a * p; }
The second version of operator* can be defined directly, but in this example it is defined in terms of the first version.