{{announcement.body}}
{{announcement.title}}

Memory Leak: Bit-Wise and Member-Wise Copy in C++

DZone 's Guide to

Memory Leak: Bit-Wise and Member-Wise Copy in C++

In this article, how we can better use Orthadox Canonical Form in C++ to better manage memory leaks in your application.

· Web Dev Zone ·
Free Resource

Earlier versions of C++ (prior to release 1.2) implemented assignment between objects through bit-wise copy of the source to the destination. Consider the following example that illustrates a bit-wise copy.

C++
 




xxxxxxxxxx
1
11


 
1
class MyString {    
2
  public:  
3
   
4
     MyString(int n)  { size = n; rep = new char[size]; } 
5
     ~MyString()      { delete [] rep; } 
6
     int length() const      { return size; } 
7
          
8
 private: 
9
    char* rep;     
10
    int size; 
11
  }; /* MyString */



The main program has two objects of class MyString.

C++
 




xxxxxxxxxx
1


 
1
int main() {     
2
   MyString s1(10), s2(20);     
3
   s1 = s2;     
4
   return 0; 
5
} /* main */



The program creates two MyString objects, s1 and s2, by invoking the constructor, MyString(int). As a result, s1 contains a pointer to a character string of length 10, and s2 contains a pointer to a character string of length 20. Following the assignment operation in the main program, s1 and s2 assume exactly the same value, field per field. This leaves both s1.rep and s2.rep pointing to the same character string (of length 20), while the pointer to the character string that was originally present in s1.rep has been lost, thus implying a leak.

Furthermore, when the destructors for objects s1 and s2 are invoked, the character string pointed to by both s1.rep and s2.rep will be released twice leading to abnormal program termination, which in large programs, can lead to debugging nightmares.

To circumvent these problems, programmers had to suitably redefine the operations that caused bit-wise copying — namely, the copy constructor and the assignment operator.

To overcome this problem, one needs an alternate approach that can make both assignment and passing of objects by value as straightforward as with built-in types. The orthodox canonical form, presented below, addresses this problem.

The Orthodox Canonical Form

The orthodox canonical form is an important programming idiom in C++, which allows objects to be declared, passed, and assigned like any C variable. Refer to the class definition appearing below that confirms to this idiom.

C++
 




xxxxxxxxxx
1
14


 
1
class MyString {    
2
  public: 
3
     // the public user interface for the class MyString 
4
     int length() const;  // length of string in chars 
5
 
          
6
     // member functions 
7
     MyString();                // default constructor 
8
     MyString(const MyString&); // copy constructor 
9
     MyString& operator=(const MyString&);  //assignment operator 
10
     ~MyString();   // destructor     
11
 private: 
12
    char* rep;   // MyString uses a private attribute     
13
                 // that points to a C string 
14
  }; /* MyString */



What do the methods of the class MyString do? This is discussed below:

C++
 




xxxxxxxxxx
1


 
1
MyString():



This method represents the default constructor for the class and is invoked in order to initialize the class members with their respective default values when no initialization context is provided.

In this case, it is defined to initialize an object of the class with a null (empty) string, as defined below:

C++
 




xxxxxxxxxx
1


 
1
MyString::MyString() {
2
     rep = new char[1];
3
     rep[0] = ‘’;
4
 } /* MyString */



Whenever an object of type MyString is created (either on the stack or on the heap), the default constructor is invoked by the compiler automatically. If the user does not define a default constructor, then the compiler creates one on behalf of the user. 

Although this constructor invokes the respective default constructors for all the objects declared within this class, other class members will go un-initialized (e.g. rep  in class, MyString).

C++
 




xxxxxxxxxx
1


 
1
MyString(const MyString&):



The copy constructor, which takes as input the reference to an already constructed object of the class, is used for cloning a MyString object in order to create an exact replica of the object, as outlined below.

C++
 




xxxxxxxxxx
1


 
1
MyString::MyString(const MyString& s) {
2
     // allocate space on the heap; leave room for ‘’
3
     rep = new char[s.length() + 1];     // copy the character string from the input object
4
     rep[0] = ‘’;
5
     ::strcpy(rep, s.rep);  // :: implies global scope
6
 } /* MyString */



This constructor copies every attribute of the source object into the corresponding attribute of the destination object, thus realizing initialization through copying. Here, it is important to highlight the two modes of copying that can be used, while implementing a copy constructor — viz. shallow copy and deep copy.

In shallow copy, the fields of the source object are copied member-wise into the fields of the destination object, without creating replicas of the objects referred to (or pointed by) the attributes of the source object.

However, in deep copy, all objects referred to (or pointed by) the attributes of the source object are physically replicated as they are copied into the destination object, and this process is repeated recursively down to the lowest level of the objects referred to from (or pointed to by) the source object.

C++
 




xxxxxxxxxx
1


 
1
MyString& operator=(const MyString&):



The assignment operator is very much like the copy constructor, but it has a return value that is a reference to the type (class) to which it belongs. Being a reference helps save the overhead of creating an intermediate, temporary copy. In addition, the operator must take care of releasing all memory held by the destination object into which it copies the value being assigned. The orthodox canonical form, through its guarantee that a constructor would have been invoke in order to acquire memory earlier on, ensures that it is safe to perform a cleanup in the definition of operator=.

Summary

To sum up, the orthodox canonical form, for any class X, requires that the following functions be defined for that class.

  • The default constructor, X::X().
  • The copy constructor, X::X(const X&).
  • The assignment operator, X& X::operator=(const X&).
  • The destructor, X::~X().

It is good to employ the orthodox canonical form as it provides ample protection against memory leaks and accidental freeing of resources when classes attributes contain pointers to data.

Topics:
c++ programming ,deep copy ,memory leak ,memory leak tutorial ,memory leaks ,shallow copy

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}