Please refer below program before answering question. Explained the code in comments.
So my question here is in assignment operator overloading how to handle the case where new() failed to allocate memory.
For example Obj1 is holding string "GeeksQuiz". Assigning Obj2 to Obj1. During assigning (in assignment operator overload function) first we free Obj1 and then recreate Obj1 with Obj2 values. So in the case where new fails to allocate memory how to retain old Obj1 values? Since we freed Obj1 values in starting of function.
All that I want is to have the old values for Obj1 when the assigning operation fails.
Please help me in this. I want perfect code, without any memory leaks covering all scenarios. Thanks in Advance
#include<iostream>
#include<cstring>
using namespace std;
class String
{
private:
char *string_data;
int size;
public:
String(const char *str = NULL); // constructor
~String() { delete [] string_data; }// destructor
void print() { cout << string_data << endl; } // Function to print string
String& operator = (const String &); // assignment operator overload
};
String::String(const char *str) // Constructor
{
size = strlen(str);
string_data = new char[size+1];
if (string_data != NULL)
strcpy(string_data, str);
else
cout<<"compiler failed to allocate new memory";
}
String& String::operator = (const String &str) // assignment operator overload
{
if(this != &str)
{
delete [] string_data; // Deleting old data and assigning new data below
size = str.size;
string_data = new char[size+1];
if(string_data != NULL) // This condition is for cheking new memory is success
strcpy(string_data, str.string_data);
else
cout<<"compiler failed to allocate new memory"; // My quetsion comes in this scenario...
}
return *this;
}
int main()
{
String Obj1("GeeksQuiz");
String Obj2("stackoverflow");
Obj1.print(); // Printing Before assigment
Obj2.print();
Obj1 = Obj2; // Assignment is done.
Obj1.print(); // Printing After assigment
Obj2.print();
return 0;
}
First of all, implementing a robust string is difficult, unless you want to do it for learning purposes always use std::string.
Then take into account that operator new always returns non-null pointers (unless you are also implementing a non standard custom new operator), instead it throws a std::bad_alloc exception if it fails to allocate the data. If you want to handle the allocation fail case you need to add a try-catch block
char *data = NULL;
try {
data = new char[str.size + 1];
} catch (std::bad_alloc &e) {
std::cout << "Allocation failed: " << e.what() << std::endl;
throw; // <- You probably want to rethrow the exception.
}
strcpy(data, str.string_data);
delete [] string_data;
string_data = data;
size = str.size;
The important part is to leave your class in a consistent state when the exception is thrown, that's why you must first allocate the new data and then if it succeeds, delete the old data. However bad_alloc exceptions are rarely handled at class level, commonly you let the exception be thrown (that's why I rethrow in the code sample) and let the client code handle that.
If you really want your code to be exception proof I would advice the use of smart pointers, and as already said, in this case use std::string.
Temporary or dummy variables.
Allocate new memory, assign pointer to a temporary variable. If it succeeds then free the old memory and reassign that pointer variable.
Pseudo-ish code:
char *temp = new char[new_size];
std::copy(new_data, new_data + new_size, temp);
delete [] old_data;
old_data = temp;
old_size = new_size;
1st allocate memory in a temporary variable, if it's successful then only delete old value.
Related
I have to write a code that gets a string and turns it into an object of a class. Everything is working as expected but I'm unable to deallocate the dynamically allocated 2d array of objects.
I know the issue is within the destructor and the Move assignment operator for the object, I keep getting SIGBRT and EXC_BAD_ACCESS errors when I try to run it.
Below is my Code for the constructor, destructor and move assignment/constructor
//CustomerOrder.cpp
CustomerOrder::CustomerOrder(std::string&
src):Name(src),Product(),ItemCount(),ItemList(),field_width(){
std::vector<ItemInfo> info;
std::string* tokens[] = { &Name, &Product };
Utilities utils;
size_t next_pos = -1;
bool more = true;
for (auto& i : tokens) {
if (!more) break;
*i = utils.extractToken(src, next_pos, more);
}
while (more){
info.push_back(utils.extractToken(src, next_pos, more));
}
if(!info.empty() && info.back().ItemName.empty()){
info.pop_back();
}
ItemCount = info.size();
ItemList = new ItemInfo*[ItemCount];
for (int i = 0; i < ItemCount; i++){
ItemList[i] = new ItemInfo(info.at(i).ItemName);
}
if (utils.getFieldWidth() > field_width){
field_width = utils.getFieldWidth();
}
}
CustomerOrder::~CustomerOrder(){
for(int i = 0; i<ItemCount;i++){
delete[] ItemList[i];
}
delete[] ItemList;
}
CustomerOrder::CustomerOrder(CustomerOrder&& src){
*this = std::move(src);
}
CustomerOrder& CustomerOrder::operator=(CustomerOrder&& src){
if(this!= &src){
delete [] ItemList;
Name = std::move(src.Name);
Product = std::move(src.Product);
ItemCount = std::move(src.ItemCount);
ItemList = std::move(src.ItemList);
src.ItemList = nullptr;
}
return *this;
}
And the ItemInfo struct
//ItemInfo struct
struct ItemInfo
{
std::string ItemName;
unsigned int SerialNumber;
bool FillState;
ItemInfo(std::string src) : ItemName(src), SerialNumber(0),
FillState(false) {};
};
You are combining "new" with "delete[]". If you use "new" use "delete" if you use "new[]" then use "delete[]" for the thing.
This is your problem there: "delete[] ItemList[i];" it should be "delete ItemList[i];" instead
This line of your code ItemList[i] = new ItemInfo(info.at(i).ItemName); doesn't allocate a dynamic array, yet this code in your destructor tries to delete it as thought it was a dynamic array.
for(int i = 0; i<ItemCount;i++){
delete[] ItemList[i];
}
A quick fix would to be to change delete[] to delete. However, it appears as though it would be much easier to simply allocate a single dynamic array. In other words, allocate ItemList as such ItemList = new ItemInfo[ItemCount]; Granted, you would have to change the type, but it makes more sense from what you posted.
Another possible issue is that in your destructor you don't check if the ItemList is a nullptr or actually allocated to anything. To which, your destructor could possibly try to access invalid data. Not only that, but your move operator deletes the ItemList without deleting the data inside of it.
You could make a function to free up the data in ItemList and then call that function from the destructor and move operator.
On a side note, why are you using dynamic 2D arrays when it appears that you know how to use vectors? A vector would handle all of this in a much simpler fashion. For example, the type would be std::vector<std::vector<ItemInfo>>.
#include <iostream>
#include<cstring>
using namespace std;
class String
{
private:
char *sptr;
public:
String()
{
}
String(char str[])
{
sptr = new char[strlen(str) + 1];
strcpy( sptr, str );
}
String(const String& source)
{
sptr = new char[strlen(source.sptr) + 1];
strcpy( sptr, source.sptr);
}
~String()
{
delete sptr;
}
String operator=( const String& other )
{
if(&other!=NULL)
{
String tmp( other );
sptr = new char[strlen(tmp.sptr) + 1];
strcpy( sptr, tmp.sptr);
}
return *this;
}
void display()
{
for( char const* p = sptr; *p != '\0'; ++ p) {
std::cout << *p;
}
cout<<endl;
}
};
int main()
{
String a1;
String a2("Hello ");
String a3(a2);
a2.display();
a3.display();
//a2.change("Java ");
a2.display();
a3.display();
}
The Output pf the program is
Hello Hello Hello Hello.
But I am undone to do the following changes in my code... i.e.. In Main () I created the following objects
String a1;
String a2("Hello ");
String a3(a2);
a2.display();
a3.display();
//a2.change("Java ");
a2.display();
a3.display();
I want to change the the object a2(commented) with Java and desired the output as
Hello Hello Java Hello
through deep copy of the Data members through this pointers.
How can I change the Hello string to java
Your String class cannot be used to build a program upon, due to the faults within the String class. You must fix those errors first before even thinking about using String as a string class.
Therefore this answer addresses how to fix String first -- from there, you should now be able to write the program knowing you're not working on a faulty foundation.
Error 1: Default construction
You failed to initialize sptr when a String is default constructed. Thus when ~String() is executed, the call to delete will attempt to delete an uninitialized pointer.
Simple 1 line main functions such as this one:
int main()
{
String s;
} // <-- destructor of s may crash
will show undefined behavior and may crash, as shown by this example.
To fix this:
class String
{
public:
String() : sptr(nullptr) {}
//...
};
Now when delete [] is issued on sptr, no harm will occur since it is perfectly valid to delete [] a nullptr (it becomes basically a no-op).
Error 2: Faulty assignment operator
Your String::operator= fails to deallocate the previous memory that was allocated. In addition, the check for NULL is incorrect, since &other will always be true.
I am assuming this is the check for self-assignment, but it is not written correctly. The check for self-assignment is done by comparing the address of the current object (this) with the address of the object being passed in:
if (this != &other)
Once you have that, then you could have written the rest of the function this way:
if(this != &other)
{
String tmp( other );
std::swap(tmp.sptr, sptr);
}
return *this;
All this function does is copy other to a temporary String called tmp, swap out the sptr with the tmp.sptr and let tmp die off with the old data. This is called the copy / swap idiom, where you're just swapping out the contents of the current object with the contents of the temporary object, and letting the temporary object die off with the old contents.
Note that the check for self-assignment isn't necessary when using copy / swap, but there is no harm done in making the check (could even be a small optimization done when the check for self-assignment is present).
Edit: The other issue is that operator= should return a reference to the current object, not a brand new String object. The signature should be:
String& operator=( const String& other ) // <-- note the return type is String&
{
//... code
//...
return *this;
}
Error 3: Wrong form of delete
Since you allocated using new [], you should be using delete [], not just delete in the destructor:
~String()
{
delete [] sptr;
}
Given all of these changes, the following code no longer has issues, as shown by the Live Example here.
Now that you have a working String class, then you can build your application from there.
As to your program, you can easily change your string by using the assignment operator. There is no need for a change() function:
String a2("Hello ");
a2.display();
a2 = "Java ";
a2.display();
Since String::operator=(const String&) is no longer faulty with the changes above, assignment can now be done without issues.
I Have a doubt in the following code, there is a destructor delete line[] inside the destructor, I just want to know if there any stack over flow for this delete which can be a result of recursive call to destructor.
class Line {
public:
char *line;
Line(const char *s = 0) {
if (s) {
line = new char[strlen(s)+1];
strcpy(line, s);
} else {
line = 0;
}
}
~Line() {
delete[] line; //----------> how this delete will work?
line = 0;
}
Line &operator=(const Line &other) {
std::cout <<"go"<< endl;
delete[] line; //----------> purpose of using this delete??
line = new char[other.len()+1];
strcpy(line, other.line);
return *this;
}
int operator<=(const Line &other) {
int cmp = strcmp(line, other.line);
return cmp <= 0;
}
int len() const {
return strlen(line);
}
};
int main() {
Line array[] = {Line("abc"), Line("def"),
Line("xyz")};
Line tmp;
}
The delete inside the overloaded assignment operator is to clean the memory before assigning new memory(I have read it somewhere, correct me if I am wrong), but does this delete will call the destructor?
Please Explain
No, it will not.
This delete statement will delete the char array. The destructor of Line is only called when destroying the Line object. That is however not the case here.
The variable line and the object/class Line are different things.
The line variable is a member-variable in the Line class. So those two names seem the same but are completly different.
delete[] line; pairs the new char[strlen(s)+1]; statements in the constructor and assignment operator. Note that delete[] line; is a no-op if line is set to nullptr, which is what the else branch assignment does, although it sloppily uses 0 in place of nullptr.
Be assured the destructor is not called recursively. It's just that the destructor is used to release any allocated memory.
But using std::string line; as the class member variable, or perhaps even the entire class itself would be far, far easier. There are some subtle bugs in your code - self assignment being one of them, and the copy constructor is missing. Let the C++ Standard Library take care of all this for you. In short you could write
int main() {
std::string array[] = {"abc", "def", "xyz"};
std::string tmp;
}
The argument to delete[] is a char*, ie there is no destructor called (and also there is no recursive calling of the destructor).
If you had a destructor like this:
~Line() { delete this; } // DONT DO THIS !!! (also for other reasons it is not OK at all)
this would attempt to call itself recursively, but your code looks fine.
In the assignment operator
line = new char[other.len()+1];
will allocate new memory and assign a pointer (pointing to this memory) to line. This will cause that you have no handle to the old memory anymore and to avoid a leak you need to delete it before.
C++ by default takes care of delete[] for char*, therefore you do not need to do anything.
I have a class with assignment operator as below.
char *buff;
myString& operator= ( const myString& other )
{
cout << " myString::operator=\n";
if( this != &other ){
cout<<"my string ="<<endl;
delete [] buff;
length = other.length;
buff = new char[length];
my_strncpy( buff, other.buff, length );
}
return *this;
}
I am deleting memory for buff and allocating with length of new string. How can I handle any exception that happens during the allocation memory with new? How can I restore the value of buff to old values incase of exception?
There are two solutions to this. The first (best) is to use copy-and-swap:
myString& operator= ( myString other ) {
swap (*this, other);
return *this;
}
If allocation fails in the copy constructor, we'd never get to the swap, so there's no worry about overwriting our current state. For more, see What is copy-and-swap?
The other approach is to just make sure you only delete if it's safe. That is, do it after the new:
tmp_buff = new char[other.length];
// either that threw, or we're safe to proceed
length = other.length;
my_strncpy(tmp_buff, other.buff, length);
delete [] buff;
buff = tmp_buff;
Dealing with out-of-memory conditions is hard since there often is no easy failure path that can be used.
In your case, you could try creating the new buffer before deleting the old buffer but this may increase the likelyhood of running out of memory (OOM).
Ideally you should probably use the old buffer if it is big enough and only create a new buffer if the old one is too small. In such a case it is probably ill advised to use the old buffer in the event of OOM since it will be too small to store the string.
How can I handle any exception that happens during the allocation memory with new?
A try{} catch(){} should be used to catch exceptions thrown during allocation and object creation.
How can I restore the value of buff to old values incase of exception?
The key here is not really to restore the old values if an exception is thrown, but to first allocate and copy the contents, swapping pointers etc. and then delete the old object and memory. In this way if an exception is thrown, the current contents are left unchanged.
First allocate the memory to a temporary pointer, copy the data required, then swap the new pointer for the old one and delete the old data; akin to the copy-swap idiom. GotW (#59) also has a nice article on this here.
myString& operator= ( const myString& other )
{
if( this != &other ){
try {
char* temp_buff = other.length ? new char[other.length] : nullptr;
// I assume my_strncpy handle NULL pointers etc.
// If not, call it behind an if check for length and pointer validity
my_strncpy( temp_buff, other.buff, length );
std::swap(temp_buff, buff);
delete [] temp_buff;
length = other.length;
}
catch (std::bad_alloc& e) {
// deal with the bad_alloc...
}
catch (std::exception& e) {
// deal with the exception
}
}
return *this;
}
In general, out of memory conditions are significant, so just catching the exception may not always be ideal - it needs to be dealt with by the application as a whole, possibly even by the user for the entire system. A question to ask yourself is; what are you going to do with the exception, how are you going to recover from it?
A more general solution (I assume you really are focused on implementing the operator= in the current form) is to use a full blown copy-swap implementation.
class myString {
char* buff;
std::size_t length;
// ...
};
myString::myString(myString const& src) :
buff(src.length ? new char[src.length] : nullptr),
length(src.length)
{
if (length)
std::copy(src.buff, src.buff + length, buff);
}
myString::~myString()
{
delete [] buff;
length = 0;
}
void myString::swap(myString& rhs)
{
std::swap(rhs.buff, this->buff);
std::swap(rhs.length, this->length);
}
myString& myString::operator=(myString const& rhs)
{
if (this != &rhs) {
myString temp(rhs);
swap(temp);
}
return *this;
}
// the rest of the class implementation
//... non-member swap for addition utility
inline void swap(myString& lhs, myString& rhs)
{
lhs.swap(rhs);
}
Here is the program...
class CopyCon
{
public:
char *name;
CopyCon()
{
name = new char;
}
CopyCon(const CopyCon &objCopyCon)
{
name = new char;
_tcscpy(name,objCopyCon.name);
}
~CopyCon()
{
if( name != NULL )
{
delete name;
name = NULL;
}
}
};
int main()
{
CopyCon objCopyCon1;
objCopyCon1.name = "Hai";
CopyCon objCopyCon2(objCopyCon1);
objCopyCon1.name = "Hello";
cout<<objCopyCon2.name<<endl;
return 0;
}
Once the code execution completes, when the destructor called, it crashes on 'delete' saying...
Debug Error!
Program: ...
HEAP CORRUPTION DETECTED: after Normal block (#124) at 0x00366990.
CRT detected that the application wrote to memory after end of heap buffer.
(Press Retry to debug the application)
Don't we have to clear the heap memory in destructor. What's wrong with this program? Pls someone help!
Copy constructor works perfectly as intended. But still... !?
The problem is you are allocating only one char in the copy constructor.
In main you are assigning a 4-byte string (remember the null), but when you copy the object, you only allocate enough room for 1 byte.
What you probably want to do is change
name = new char;
to
name = new char[tcslen(objCopyCon.name) + 1];
And in the destructor:
delete name;
to
delete [] name;
Also:
You are assigning "Hai" and "Hello" to objCopyCon1.name which is hiding the memory allocated in the constructor. This memory can never be freed!
You write past the allocated variable and that is undefined behavior.
When the folloing lines run
CopyCon objCopyCon1;
objCopyCon1.name = "Hai";
CopyCon objCopyCon2(objCopyCon1);
_tcscpy() copies 4 characters (3 letters and the null terminator) into a buffer that can legally hold only one character. So you write past the buffer end and this leads to heap corruption.
You need to alocate the buffer of the right size:
CopyCon(const CopyCon &objCopyCon)
{
name = new char[_tcslen(objCopyCon.name) +1];
_tcscpy(name,objCopyCon.name);
}
also you need to change the delete in the destructor to delete[] and also change all other new calls to new[] to avoid undefined behavior.
You are allocating one character and trying copy multiple characters into that memory location. First find out the length of the string then allocate length + 1 characters (extra char to accommodate the NULL character) using new char[length+1] syntax. You need to correspondingly change your destructor to delete[] name.
Besides the new char issue that everyone mentioned, the strings "Hai" and "Hello" reside in read-only memory. This means you cannot delete them (but you do so in your destructor) - this does generate crashes. Your code should not assign to name directly, but use a set function such as:
void set_name(const char *new_name)
{
delete [] name; // delete is a no-op on a NULL pointer
name = new char[tcslen(new_name) + 1];
_tcscpy(name,new_name);
}
I'm surprised that assignment does not produce a compiler warning to be honest. You are assigning a const char * to a char *, which can lead to all sorts of problems like the one you're seeing.
The job of the copy ctor should be to create a copy of the object. So the char array pointed to by name in the both the objects should be of the same size and same content, which is not happening in your case. So change
name = new char; // allocates only one char
to
name = new char[strlen(objCopyCon.name) + 1]; // allocate array of char
You need to allocate enough memory to hold the information you are trying to store. "Hai" is 4 bytes or chars (including the null terminator) and you have only allocated one. You also do not copy strings from one memory location to another using "=". You need to strncpy the string across.
Use std::string it will make your life a million times easier :)
Here the code that works perfect!
class CopyCon
{
public:
char *name;
CopyCon()
{
name = NULL;
}
CopyCon(const CopyCon &objCopyCon)
{
name = new char[_tcslen(objCopyCon.name)+1];
_tcscpy(name,objCopyCon.name);
}
~CopyCon()
{
if( name != NULL )
{
delete[] name;
name = NULL;
}
}
void set_name(const char *new_name)
{
//delete [] name; // delete is a no-op on a NULL pointer
if( NULL != name)
{
delete[] name; name = NULL;
}
name = new char[_tcslen(new_name) + 1];
_tcscpy(name,new_name);
}
};
int main()
{
CopyCon objCopyCon1;
objCopyCon1.set_name("Hai");
CopyCon objCopyCon2(objCopyCon1);
objCopyCon1.set_name("Hello");
cout<<objCopyCon1.name<<endl;
cout<<objCopyCon2.name<<endl;
return 0;
}
Thanks to all for their view points. It really helped!