Memory Management : arrays and dynamic allocation with new operator
Q. In terms of Memory Management, what error does occur?
class String
{
public:
String(const char right[]);
String& operator= (const String& right);
int length() const;
private:
char* buffer;
int len;
}
int String::length() const {return len;}
String::String(const char right[])
{
len = 0;
while (right[len] != '\0')
len++;
buffer = new char[len+1];
for (int i = 0; i < len; i++)
buffer[i] = right[i];
buffer[len] = '\0';
}
String& String::operator= (const String& right)
{
if (this != &right)
{
delete[] buffer;
len = right.length();
char* buffer = new char[len + 1];
for (int i = 0; i < len; i++)
buffer[i] = right[i];
buffer[len] = '\0';
}
return *this;
}
Answer.
I have no clue... Could you help me?
This one seems ok too.
new and it is also deleted.
Where is the memory leak?
Please let me know.
Thanks,
You need to provide a constructor which allocates the pointer member(infact all constructors of your class should do that) and destructor which deallocates it.
Also, You need to provide a copy constructor which performs a deep copy of the pointer member.
String::String(const String& obj)
{
....
}
Good Read:
What is The Rule of Three?
Also, String is an awful name for a class especially since there exists a std::string.
Rule of three: if a class defines a destructor or a copy constructor or a copy assignment operator, it probably has to define all three.
Your code vioaltes this rule by not providing a destructor and a copy constructor while providing a copy assignment operator.
There are 4 issues with your code.
Your assignment operator has the following line: char* buffer = new char[len + 1]; This is declaring a new pointer variable and not using the class member. This will allocate memory to a pointer that then goes out of scope and can never be deleted and will be a memory leak and make your class not function.
The lack of a destructor means that your class leaks memory. Your destructor is responsible for freeing the memory that your are allocating with new[].
The lack of a copy constructor means that your String class cannot be used in many situations including in an STL container. The STL containers require that types have a public copy constructor, assignment operator and desctructor.
You are not enforcing your class invariants in the face of exceptions. The goal is to make your class exception safe. In the changes I made I tried to achieve the strong exception guarantee and conserve the class state in the face of exceptions. Considering exception safety is an important aspect of creating your own classes - especially library classes such as String.
Here's the code that attempts fixes all of those issues. To keep the code short I am putting the implementation into the class rather than separating it. Of course, in real code you should use std::string.
class String
{
public:
String() : len(0), buffer(nullptr)
{}
String(const char right[]) : len(0), buffer(nullptr)
{
if(!right)
return;
int temp_len = strlen(right);
buffer = new char[temp_len+1];
len = temp_len; // only set the length after the allocation succeeds
strcpy(buffer, right);
}
// a copy constructor is essential for this class
String(const String& right) : len(0), buffer(nullptr)
{
String temp(right.buffer);
swap(temp);
}
// must have a destructor to avoid leaks
~String()
{
if(buffer) // only delete if it has been allocated
delete[] buffer;
}
String& operator= (const String& right)
{
String temp(right.buffer);
swap(temp);
return *this;
}
int length() const
{
return len;
}
private:
void swap(String& rhs)
{
std::swap(buffer, rhs.buffer);
std::swap(len, rhs.len);
}
char* buffer;
int len;
};
When your String objects are destroyed, the default constructor is called since you did not define one. The default destructor will not delete[] your char array for you, you must declare and define a destructor to do this.
Also, you will find that using strcpy()/strncpy() is much faster than a simple loop to copy each char. (at least when compiling with GCC).
Related
EDIT: I had to use unique_ptr or follow the rule-of-five I'm still learning about it so I used unique_ptr and it worked. But I have a question, the destructor now is being called twice and I think that is no problem as long as the block of memory that the pointer is pointing to is not being freed twice, Right??
I made a simple "String" class in c++ (Know it from The Cherno). So I made it and it seems to work well until I decided to add an empty constructor to be able to initialize it with no parameter but when I did that the destructor is being called twice.
// here is the header file
#pragma once
#include <iostream>
using namespace std;
class String
{
private:
//Hold the raw of chars in the heap memory
char* m_Buffer;
//The size of the buffer in heap
unsigned int m_Size;
public:
//An empty Constructor
String()
: m_Buffer(nullptr), m_Size(0)
{
cout << "created Empty." << endl;
}
// A Constructor
String(const char* string)
{
m_Size = strlen(string);
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, string, m_Size + 1);
m_Buffer[m_Size] = 0;
}
// A destructor
~String()
{
cout << "Destroy!!" << endl;
delete[] m_Buffer;
}
// Function resposable for coping
String(const String& other)
: m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_Size + 1);
}
char& operator[](unsigned int& index)
{
return m_Buffer[index];
}
friend std::ostream& operator<<(std::ostream& stream, const String& other);
};
std::ostream& operator<<(std::ostream& stream, const String& other)
{
stream << other.m_Buffer << endl;
return stream;
}
//here is the main file
#include "LclString.h"
int main()
{
String a = "asdc";
a = "ads"; // The Destructor is being called here the first time
cin.get();
} // and here is the second time
A piece of advice will be appreciated
......................................
Your destructor calls delete[] m_Buffer. A pointer may never be deleted twice. If you do delete a pointer twice, then the behaviour of the program will be undefined. You must avoid ever doing that. To achieve avoiding that, you must make sure that no two instances of the class have the same pointer value in the member.
Consider what the implicitly generated assignment operator of your class does. It copies all members from the right hand operand to the left hand operand. Can you see why that is a problem? All members include the m_Buffer pointer. The assignment operator will cause two instances of the class to have the same pointer. And when the second instance is destroyed, it deletes that same pointer again. And the behaviour of the program is undefined.
There is another related problem, the implicit assignment operator overwrites the old m_Buffer. Who's going to delete the pointer that was overwritten? No-one is going to delete it because the value was lost by the assignment. That's a memory leak.
Conclusion:
Avoid using owning bare pointers.
Avoid using new and delete directly.
If you have a user defined destructor, then you probalby also need user defined copy/move constructor and assignment operator. This is known as rule of 5 (previously rule of 3). If you use smart pointers instead of owning bare pointers, then you usually don't need a user defined destructor.
As the code below, the copy assignment operator has to check whether the input object pointers to itself or not. I wonder why copy constructor does not need to do the same check.
I am novice in C++.I would be grateful to have some help on this question.
class rule_of_three
{
char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block
void init(const char* s)
{
std::size_t n = std::strlen(s) + 1;
cstring = new char[n];
std::memcpy(cstring, s, n); // populate
}
public:
rule_of_three(const char* s = "") { init(s); }
~rule_of_three()
{
delete[] cstring; // deallocate
}
rule_of_three(const rule_of_three& other) // copy constructor
{
init(other.cstring);
}
rule_of_three& operator=(const rule_of_three& other) // copy assignment
{
if(this != &other) {
delete[] cstring; // deallocate
init(other.cstring);
}
return *this;
}
};
Self-assignment sometimes happens, it's a part of a normal use of a class.
Passing a not-yet-constructed object as a parameter to its own copy (or move) constructor is not normal. While not undefined behavior per se1, there are no good reasons to do it, and it normally doesn't happen. It can happen accidentally, or if someone is deliberately trying to break your class.
Because of that, traditionally copy (and move) constructors don't check for &other != this.
But nothing stops you from doing it, if you want some extra safety:
rule_of_three(const rule_of_three& other) // copy constructor
{
assert(&other != this);
init(other.cstring);
}
1 [basic.life]/7 seems to allow that, as long as you don't access the not-yet-constructed object itself. Taking an address of it using & is allowed.
Let's suppose you have simple objects like Stormtroopers:
class Stormtrooper
{
char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block
void clone(const char* s)
{
std::size_t n = std::strlen(s) + 1;
cstring = new char[n];
std::memcpy(cstring, s, n); // populate
}
public:
Stormtrooper(const char* s = "I am a Stormtrooper clone") { clone(s); }
~Stormtrooper()
{
delete[] cstring; // deallocate
}
Stormtrooper(const Stormtrooper& other) // copy constructor
{
clone(other.cstring);
}
Stormtrooper& operator=(const Stormtrooper& other) // copy assignment
{
if(this != &other) {
delete[] cstring; // deallocate
clone(other.cstring);
}
return *this;
}
};
If you want to assign une Stormtrooper to another it is useful to check if the two Stormtroopers are already identical or not (and they usually are). In this way you avoid the clone() operation, because the Stormtroopers are already identical.
But if you want to create a new Stormtrooper, and you want him to be identical to another Stormtrooper (as usual) you can copy-construct it and in this case the clone() operation will be performed correctly.
In this way you can create an entire army of Stormtroopers quite easily.
I understand which is invoked in what situation...
Sample a;
Sample b = a; //calls copy constructor
Sample c;
c = a; //calls assignment operator
My question is Why these 2 different things exist at all? Why can't only one of the two take care of both situations?
No, they are different.
Copy constructor is used for constructing a new object (from another object). In this case you just need to initialize the members.
Assignment operator is used for an existing object (you may have constructed it by default constructor etc), and then assign it by another object. In this case you need to re-initialize members, sometimes means destroying and initializing them again.
Even so, the functionality of them are so similar, so you can share their implementation usually. Such as: What is the copy-and-swap idiom?
The copy constructor is invoked on creation, that means you don't have to take care of old resources in your object. The assignment operator on the other hand has to free the old resources. Besides they have different semantical meaning.
Suppose the String class:
class String
{
char *m_str;
size_t m_len, m_alloced;
public:
String(const char *str = "")
{
m_len = strlen(str);
m_alloced = m_len + 1;
m_str = (char *)malloc(m_alloced);
strcpy(m_str, str);
}
String(const String &other)
{
m_len = other.m_len;
m_alloced = m_len + 1;
m_str = (char *)malloc(m_alloced);
strcpy(m_str, other.m_str);
}
String &operator =(const String &rhs)
{
if (m_alloced < rhs.m_len + 1)
{
m_alloced = rhs.m_len + 1;
m_str = (char *)realloc(m_str, m_alloced);
}
m_len = rhs.m_len;
strcpy(m_str, rhs.m_str);
return *this;
}
const char *get() const
{
return m_str;
}
};
(live example)
Appearently, copy constructor and copy assignment operator does different things. Look at the String::String(const String &other).
m_len = other.m_len;
m_alloced = m_len + 1;
m_str = (char *)malloc(m_alloced);
strcpy(m_str, other.m_str);
It initializes its object. Set m_len, m_alloced, and m_str, and strcpy the string.
However, the String &String::operator =(const String &rhs) does -
if (m_alloced < rhs.m_len + 1)
{
m_alloced = rhs.m_len + 1;
m_str = (char *)realloc(m_str, m_alloced);
}
m_len = rhs.m_len;
strcpy(m_str, rhs.m_str);
return *this;
modify its object. Allocate more memory if required, and reset m_len and recopy the string.
Copy constructor is called when the object is created, and does initialize its object.
But copy assignment operator is called after the object is created, and does modify its object.
For example, look at this code.
String str1 = "asdf";
String str2 = str1;
String str3 = "12";
str3 = str1;
(it's also in the live example)
str1 is initialized by String::String(const char *). As you know, it'll contain "asdf".
str2 is initialized by copy constructor, String::String(const String &other). By the copy constructor, str2 will contain the same content.
str3 is initialized by String::String(const char *), like str1. However, in line 4, it is modified by copy assignment operator. So, str3 contains "12" at first, but its content will be modified into "asdf", by copy assignment operator.
Well, you could technically get away with just having a copy-constructor and a destructor, but that would require that every time you wanted to do an assignment you would have to destroy the object and then reconstruct it. This would become HIGHLY inefficient in the vast majority of use cases.
We cannot just have an operator= either, as you have no idea what the left-hand-side of the operator will be if it has not yet been constructed. It's members would probably contain garbage data and "shit would hit the fan".
Thus, you can think of the need for both an operator= and a copy-constructor as an optimization.
That being said there are a few design-patterns that can reduce the amount of code you have to write. For example, assuming you have a copy-constructor, a destructor and a swap-function implemented for your class. You could implement operator= using a copy-swap operation:
MyClass & operator=( MyClass rhs ) { // notice the call by value, this will
// implicitly call the copy-constructor.
swap(*this, rhs);
return *this;
}
Whilst this operator has strong exception safety, it is worth to notice that it may be much less efficient than reusing preallocated resources (which a more advanced operator= might do).
Basically, they are used in different situations, many correct answer, i just wanna add something to clear out where the copy constructor is also used:
For example:
void test(Fraction in){
//Do something here
}
Fraction test2(){
Fraction a(1, 2);
//Do something here
return a; // construct a return value by copy a
}
int main()
{
Fraction a(2, 3);
Fraction b(a); //construct b by copy a
Fraction c=a; //construct c by copy a
test(a); //construct in by copy a
}
Our professor posted a custom 'String' template file online, and asked us a while ago to fill out the functions below. My question, in order to try and understand this, is why the top three constructors have Text = NULL; and below it, this = source;, some other form of it. I feel like each should say Text = the_input_parameter.
Many thanks, here's the code:
class String
{
public:
// Default constructor
String()
{
Text = NULL;
}
String(const String& source)
{
Text = NULL;
// Call the assignment operator to perform deep copy
*this = source;
}
String(const char* text)
{
Text = NULL;
// Call the assignment operator to perform deep copy
*this = text;
}
~String()
{
delete[] Text;
}
// Assignment operator to perform deep copy
String& operator = (const char* text)
{
// Ddispose of old Text
delete[] Text;
// +1 accounts for NULL-terminator
int trueLength = GetLength(text) + 1;
// Dynamically allocate characters on heap
Text = new char[trueLength];
// Copy all characters from source to Text; +1 accounts for NULL-terminator
for ( int i = 0; i < trueLength; i++ )
Text[i] = text[i];
return *this;
}
// Returns a reference to a single character from this String
char& operator [] (int index) const
{
int length = GetLength();
// Check for valid index
if ( (index < 0) || (index > length) )
{
stringstream error;
error << "operator[] - index " << index << " is out of bounds (0.." << (length - 1) << ")";
throw String(error.str().c_str());
}
return Text[index];
}
private:
// The encapsulated C-string
char* Text;
};
Why you should not implement constructors in terms of assignment:
It gets pretty nasty in derived classes. Think about it.
It's difficult to make exception safe.
It's also inefficient, to boot (requiring default construction then assignment).
So the answer to why it’s done that way in your example code, is possibly that your professor doesn’t know diddly about C++ programming.
Otherwise, it's difficult to say: it just doesn't make any sense at all to do that.
Going the other way, however, namely implementing copy assignment in terms of copy construction, is very common and is known as the copy-and-swap idiom.
It's simple, exceptions safe and generally efficient, and goes like this:
class Foo
{
public:
void swap_with( Foo& other ) throw()
{
// No-throwing swap here.
}
void operator=( Foo other )
{
other.swap_with( *this );
}
};
yup, that's all.
variants include naming the swapper just swap, and letting the assignment operator return a reference, and some prefer to pass the argument by reference and then make a copy (using copy construction).
It's just a way to factor out common code into a helper function. In this case, operator=() acts as the helper function. Its function is to deallocate the current string (in this case, NULL) and perform a deep copy of the right-hand side.
I feel like each should say Text = the_input_parameter.
For String(const String& source) this wouldn't compile since source isn't the right type.
For String(const char* text) this wouldn't be correct since that would just assign the pointer instead of performing a deep copy.
The above assumes you are only showing us a part of the class, and that the actual class defines an appropriate assignment operator and a destructor. If it doesn't, you need a new professor.
The class manages memory, so destructor releases it, and assignment operator allocates new for new data and releases old one (shoud be in that order).
Then the explanation of initial assignment is clear: you need to initialize the member field to a correct value, as otherwise it will contain garbage (some pointer to somewhere), which code will attempt to use and free.
Though it is not seen from the code, there might be also assignment for const String& and type cast operator operator const char *() const.
OK, very simple String class, which holds constant strings (i.e. can't be changed once initialized), implements the copy ctor and concatenation function conc. It's that function that's giving me trouble, because I really cannot figure out why a local variable I make isn't passed normally as return value.
Code:
class String
{
public:
String(char*);
String(const String& other);
~String();
friend String conc(const String&, const String&);
friend std::ostream& operator<<(std::ostream&, const String&);
private:
const char* const str;
};
String::String(char* aStr) : str(aStr) {}
String::String(const String& other) : str(other.str) {}
std::ostream& operator<<(std::ostream& out, const String& s)
{
out<<s.str<<"\n";
return out;
}
String::~String()
{
delete str;
}
String conc(const String& s1, const String& s2)
{
int n = strlen(s1.str) + strlen(s2.str);
int i, j;
char* newS = new char[n+1];
for(i = 0; i < strlen(s1.str); ++i)
{
newS[i] = s1.str[i];
}
for(j = 0; j < strlen(s2.str); ++j)
{
newS[i+j] = s2.str[j];
}
newS[i+j] = '\0'; //newS is made correctly
String tmp(newS); // the tmp object is made correctly
return tmp; // here tmp becomes something else --- Why??
}
int main()
{
String s1("first");
String s2("SECOND");
String s3 = conc(s1, s2); //here the copy ctor should be called, right?
std::cout<<s3;
_getch();
}
As you can see in the comments, the problem is in the conc function at the end. I have made the function return a String, and not String& on purpose, given that the value it returns shouldn't be an lvalue...
Please explain & help, thanks!:)
There are memory management problems all over the place here. Each String object should own its own memory. That means that String::String(char*) needs to allocate an array of char and copy the contents of the input string; String::String(const String&) needs to allocate an array of char and copy the contents of the input string; and String::operator= needs to delete its own string (unless it's the same as the input string), allocate an array of char, and copy the contents of the input string. Finally, String::~String() should delete[] the array of char.
It is not what you think: the problem is the deletion of the temp. You call delete instead of delete[] on an array allocated with new[] - an undefined behavior.
Once you fix that error, you will have other errors related to Strings initialized with string literals: passing them to delete[] is also undefined behavior.
The root cause of the problem is that your class does not let you differentiate between the memory that you must release and the memory that you must not release. You should do it uniformly, for example by always copying the content into an array that you allocate inside the constructor.
There are several problems with your class:
Your String( char * ) constructor assumes ownership of the pointer passed to it, so if you construct the object with a string literal the destructor will try to delete it, leading to undefined behavior.
Your copy constructor assumes ownership of the string belonging to the object being copied, instead of making its own copy, due to which the string will be double deleted.
In the conc function you allocate memory using new[] but then you delete it instead of delete[] leading to undefined behavior
The member variable str is supposed to be a char array, so the destructor must delete[] it instead of delete
You need to change your constructor so it's making a copy of the C string, for example:
String::String(const String& other) : str(strdup(other.str)) {}
If you use strdup above you should change your destructor appropriately, so instead of using delete you use free
String::~String() { free(str); }
It would be a good idea to change your other constructor so it's not acquiring the C string, but making a copy of it as well, this way behaviour of all constructors would be more consistent and safer, in general:
String::String(const char* aStr) : str(strdup(aStr)) {}
If you make it this way, it will work correctly whether the client code is passing you pointer allocated with malloc or new.
Replacing strdup and free with more new and delete should be easy, I'm leaving it to you as an exercise.
When you return a temporary from a function, a copy is made for the return value and the temporary is destroyed. (Sometimes the copy can be skipped via return value optimization but I don't think that's happening here).
Because your copy constructor copies the character array pointer, both objects are now pointing to the same memory. When the temporary is destroyed, it destroys the character array and now the returned object has a dangling pointer.
One of the big benefits of an immutable string class is that it can easily share buffers as you do here, without the overhead of copying. You just need a mechanism for counting the references to the buffer so it isn't deleted until the last object is deleted. You can use a std::shared_ptr with a custom deleter instead of the char * pointer.
You should also look into the Rule of Three to make sure you're implementing all the necessary functions.