I'm working with dynamic strings, and there is an issue with copy constructor and overloading.
I'm using an operator+ to overload. the overloaded function is used to interpolate two char * strings.
here's just an excerpt of the code:
MyString::~MyString()
{
delete[] text; //char *text, as my private data
}
MyString::MyString(const MyString & obj)//copy constructor here
{
text = new char[obj.len];
for (int i = 0; i < obj.len; i++)
{
text[i] = obj.text[i];
}
len = obj.len;
}
MyString MyString::operator+(MyString & s)
{
MyString temp;
temp.len = len + s.len + 1;
int i;
for (i = 0; i < len; i++)
{
temp.text[i] = this->text[i];
}
for (int i = 0; i < s.len; i++)
{
temp.text[i + len] = s.text[i];
}
temp.text[len + s.len] = 0;
return temp;//PROBLEM HERE
}
and here's my problem:
the operator+ function calls both copy constructor and destructor before returning temp
and though there IS a copy constructor what is returned, is a deleted [] array.
what's the catch? Thanks
Lets say you have the following code
MyString s1; // Initialized to something
MyString s2; // Initialized to something else
MyString s3 = s1 + s2;
Then the expression s1 + s2 creates a temporary copy (the one returned by your operator+ function). This temporary copy is then passed to the copy-constructor to create s3 followed by the destruction of the temporary object.
That's the theory anyway, in reality the compiler will elide the copying.
Make these changes and try :
1. temp.text[len + s.len] = '\0'; //Null terminated
MyString**&** MyString::operator+(MyString & s)
{
.
.
.
}
//Return by reference, you will get the correct result.
Related
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;
class MyString
{
char* str;
int size;
public:
MyString()
{
str = '\0';
size = 1;
}
MyString(const char* const s)
: str(new char[strlen(s)])
{
size = strlen(s) + 1;
strcpy(str, s);
}
MyString(const MyString& another)
: str(new char[another.size])
{
size = another.size;
for (int i = 0; i < size; i++)
str[i] = another.str[i];
}
~MyString()
{
delete[] str;
}
void set(const char* st)
{
size = (int)strlen(st) + 1;
str = new char[size];
for (int i = 0; i < size; i++)
str[i] = st[i];
}
bool isEqual(const MyString& other) const
{
if (size != other.size)
return false;
if (strcmp(str, other.str) == 0)
return true;
else
return false;
}
void print() const
{
for (unsigned int i = 0; i < size; i++)
cout << str[i];
cout << endl;
}
};
int main() {
MyString strs[] = {
MyString("C"),
MyString(),
MyString("Java")
};
strs[1].set("C++");
const int arraySize = sizeof(strs) / sizeof(MyString);
const MyString target("Java");
for (int i = 0; i < arraySize; i++) {
const MyString str(strs[i]); // copy constructor
if (str.isEqual(target)) {
cout << "[" << i << "]: ";
str.print();
break;
}
}
for (const MyString& str : strs) {
str.print();
}
}
In dev c++ it's working and there was no caution, but in visual studio 2019, catution like "heap corruption detected after normal block crt detected that the application" is occured. I don't what is problem.
When I debuged my code, it successfully worked befor the main fuction finished. When destructor worked, the caution was appeared.
Please help me :)
Your default constructor should not compile with recent GCC compilers, you can only assign NULL or nullptr to pointer
MyString() {
str = nullptr;
size = 0;
}
This constructor needs one extra 1 byte to allocate memory, then the buffer overflow can be avoided.
MyString(const char* const s) : str(new char[strlen(s) + 1]) {
size = strlen(s) + 1;
strncpy(str, s, size);
}
strncpy is a safer replacement for strcpy, I suggest you use this one.
You also need to add implementation for operator=, or the compiler-generated one will cause a memory leak once you use operator= to copy an object.
MyString& operator=(const MyString& another) {
if (&another == this) return *this;
delete[] str;
str = new char[another.size];
size = another.size;
// better to use strncpy here
for (int i = 0; i < size; i++) str[i] = another.str[i];
return *this;
}
A similar string implementation is discussed in this question, you may also reference the code there.
Demo
One problem is the initialization of the members in the default constructor:
str = '\0';
size = 1;
The character literal '\0 is equal to zero, which is equal to a null pointer. That assignment is equivalent to:
str = nullptr;
And yet you still say that the size of the string is 1, which is wrong since there is no string and the size should be zero.
Another (and the likely culprint of your problem) problem is that in your constructor MyString(const char* const s) you use strlen to get the length of a C-style null-terminated string for the allocation, but you forget that strlen doesn't count the null-terminator.
This means the allocation will be too small, and when you use strcpy to copy the string it will write the null-terminator out of bounds of your allocated memory.
Furthermore, you in other places count with the null-terminator, and actually set the size to include the null-terminator (even in the MyString(const char* const s) constructor). This goes against the normal C++ semantics of strings where the size doesn't include the terminator.
This could then lead to further problems, like in your print function where you actually print the null-terminator.
//prototype
void Split(char c, vector <MyString> &outputVector) const
//partial code inside split function
// create new MyString object to push into output vector
MyString substr;
substr.mString = newString;
substr.mLength = size;
// push new item
outputVector.push_back(substr);
After I step over the outputVector.push_back() line, the mString data member is not retained.
//I have two constructors
MyString()
{
mString = NULL;
mLength = 0;
}
/*************************************************
* MyList copy constructor
* creates a deep copy of a MyString item
************************************************/
MyString(const MyString ©)
{
mString = new char[copy.mLength];
int i;
for(; i < copy.mLength; i++)
{ mString[i] = copy.mString[i]; }
mString[i] = '\0';
mLength = copy.mLength;
}
You are using an uninitialized variable which is undefined behavior
int i;
for(; i < copy.mLength; i++)
Here we have no idea what i is so anything can be going on but most likely i is larger than copy.mLength so we never enter the for loop. In order to get correct behavior set i to 0 like
int i = 0;
You have another issue with
mString[i] = '\0';
By the time we reach that line i == copy.mLength but the array only has the size of copy.mLength so we are one past the end since arrays are 0 index based. Most likely you need to change your allocation to
mString = new char[copy.mLength + 1];
to give you space for the null terminator.
http://www.cplusplus.com/reference/vector/vector/push_back/
push_back copies value to vector. Does MyString class have properly defined copy constructor, that copies mString member? I would guess this might be your problem.
I think there are 2 mistakes ,you have done
1.for(i; i < copy.mLength; i++)
{ mString[i] = copy.mString[i]; }
you have to mention ,where the loop will start .
2.mString = new char[copy.mLength + 1];
mString[i] = '\0';
i think ,you got the answer :)
Correct version of copy constructor
MyString(const MyString ©)
{
mString = new char[copy.mLength + 1];
int i = 0;
for(; i < copy.mLength; i++)
{ mString[i] = copy.mString[i]; }
mString[i] = '\0';
mLength = copy.mLength;
}
I've been attempting to troubleshoot this code with no avail. It crashed just as I call the copy constructor. In other attempts at calling the copy constructor the output of the new object has been random characters.
I have a feeling that my lack of understanding of memory management carries over to overloading the = operator too.
Here is my copy constructor....
MyString::MyString(const MyString& obj)
{
delete [] str;
int temp=obj.len;
len = temp;
str = new char[len];
for (int i = 0; i < len; i++)
{
char temp=obj.str[i];
str[i] = temp;
}
}
Here is my overloaded = ....
MyString& MyString::operator=(const MyString& obj)
{
delete [] str;
int temp = obj.len;
len = temp;
str = new char[len];
for ( int i = 0; i<=len; i++)
{
char temp = obj.str[i];
str[i] = temp;
}
str[len] = '\0';
return *this;
}
in the main method I call the copy constructor like so...
MyString stt3(str2); //str2 already exists.
Can anyone suggest where I'm going wrong?
The line
delete [] str;
in the copy constructor is not right. str is not initialized before that line. Removing that line should help, if not remove all the problems.
Also, you probably need to allocate one more object than len and null terminate str.
str = new char[len+1]; // Need +1
...
str[len] '\0';
You also need to change the call to new char[] in the assignment operator function too. You need:
str = new char[len+1];
I'm currently implementing my own string class (just for training), but I'm experiencing some problems in my substr() method:
MyString MyString::substr(size_t position, size_t length)
{
if (checkBounds() || length == 0)
{
return MyString();
}
char* tmp = new char[length + 1];
memcpy(tmp, this->s + position, length);
tmp[length] = STRING_ESCAPE;
MyString result(tmp);
delete[] tmp;
tmp = nullptr;
return result;
}
When I call this method and print the return value (I'm printing the char array actuallay, not the object itself), I receive complete garbage, which is carried out as a bunch of squares.
But when I return a temporary object return MyString(tmp), everything works fine. Initially i suspected this issue is associated to the delete[] operation, but commenting it out shows no effect.
The MyString constructor which is called is the following:
MyString::MyString(const char* s)
{
size_t length = this->strlen(s);
this->sLength = length;
this->s = new char[length + 1];
for (size_t i = 0; i <= length; ++i)
{
this->s[i] = *s;
++s;
}
}
So where is my mistake? Thank you!
In the two places you create MyString, they are being created within the context of your substr function. The return statement has to make a copy of them. Your copy constructor is probably not doing what you would need it to do.
A simpler design is for your substr function to return a pointer to a MyString that you create with operator new.
You were indeed missing the copy constructor. Something like this would do the work:
MyString::MyString(const MyString& other) {
size_t length = strlen(other.s);
s = new char[length+1];
strcpy(s,other.s);
this->sLength = length;
}
#include<iostream>
using namespace std;
class MyString{
private:
char *str=new char[10];
public:
MyString(){*str='\0';} //default constructor
MyString(char *s){ //parameterized constructor
str=s;
}
private:
int length(char* s){
int i=0;
while(s[i]!='\0')
i++;
return i;
}
char* delchar(char* s,int count,int start){
int i,j=0;
char *temp= new char[10];
for(i=start;i<start+count;i++){
s[i]=' ';
}
for(i=0;i<length(s);i++){
if(s[i]!=' ')
temp[j++]=s[i];
}
s=temp;
return s;
}
public:
MyString operator-(MyString s){
int i=0,j=0,count=0,start=-1;/* i to iterate the first string,j to iterate the second string*/
MyString temp; /* count to count the matched characters ,start to know the starting index*/
temp.str=str;
while(temp.str[i]!='\0'){
j=0;
start++;
while(s.str[j]!='\0'){
if(temp.str[i]==s.str[j]){
count++;
i++;
j++;
if(count==length(s.str)){//checks if the count
temp.str=delchar(temp.str,count,start);
i=i-count;
start=i-1;
count=0;
}
}
else{
i++;
count=0;
break;
}
}
}
return temp;
}
~MyString(){
delete str;
}
friend ostream &operator<<(ostream &stream,MyString& s){
stream<<s.str<<endl;
return stream;
}
};
int main(){
char *p= new char[20];
char *q= new char[10];
cin>>p;
cin>>q;
MyString s1(p);
MyString s2(q);
MyString s3;
s3=s1-s2;
cout<<s3;
delete p;
delete q;
return 0;
}
The above code overloads the - operator .It tries to subtract the substring from the main string for example input1:joshmathews input2:josh output:mathews. I am trying to store the output in a new MyString object s3. When I use a destructor as shown above,outputting s3
returns null. But when I don't use a destructor I get the expected output.Can anyone help?
The primary issue is that Operater- returns a local object which is copied by a default copy constructor -- that default copy constructor points s3 to the exact same memory/buffer of the temp MyString, and when that temp is destructed, it wipes out the memory s3 is using.
That is referred to as a dangling pointer. You can read more here: http://en.wikipedia.org/wiki/Dangling_pointer#Cause_of_wild_pointers
The code below is the changes I made to get your program executing and returning a valid result, while completing without error. To be clear though, there are issues with this altered version, runtime error or no runtime error, but this will help illuminate some important points.
When you define a type that allocates memory, you've really started to do somethings that don't come free. My altered version below completely got rid of the destructor, so actually it leaks memory up until the program ends. The destructor was invalid though, so removing it allowed the program the finish. But this would be one of those things that in general you shouldn't just accept.
I added a copy constructor and a copy assignment operator:
MyString(const MyString& s) {
strcpy_s(str, 10, s.str);
}
MyString& operator=(const MyString& s) {
strcpy_s(str, 10, s.str);
return *this;
}
Notice the strcpy_s in both of these. What is doing is copy the character string from the argument instead of just trying to point at the exact same string at the exact same address as the argument. If the argument gets destructed in your version, it wipes out some memory, so we can just accept default copy constructor and such since by default they are shallow copies that point to the same guts. That's one of the burdens of allocating memory -- you need to take care of that in both your destructor's ~and~ some of your constructors. This is referred to as "The Rule of Three":
If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.
Here's a wikipedia link about the rule of three: http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)
You needed a destructor, so that's the clue you need the others. The reason is as I was saying -- by default you get a copy constructor for free, but it just makes everything point to the same guts if there are points are resources, but then you can't delete one without affecting everybody else.
Also, in delchar I added this line towards the bottom:
temp[j] = '\0';
Characters strings must always end at a null, and you copied only actual letter characters into temp, so without a null character, a strcpy doesn't know where to end.
If you remember I yanked out your destructor, then those are the changes I made.
The destructor also has an issue, in that if you use new to create an array, like
char* c = new char[10];
then you need to delete it in a way that indicates in was new'd as an array, like so:
delete [] c;
The next thing you should look into is how your MyString construction happens. If you step through, you'll see that the str member get new'd -- that means it holds a valid address into a buffer you can use -- but then if the MyString(char *s) constructor is used, it literally just takes this new address of s, and makes str hold that address instead -- which means the old address pointing to the valid buffer is lost, and that buffer of new'd memory cannot be freed. So that's a leak. You could use the strcpy_s strategy from the constructors I added to copy the contexts of what is pointed to by the s in MyString(char *s) into the new'd buffer. That would make a huge positive difference in the code.
And don't bother about all the elitists out there -- many of them were born doing headstands, so they can't see to relate to newbies giving things an honest effort.
Here's the full altered code:
#include<iostream>
using namespace std;
class MyString{
private:
char *str = new char[10];
public:
MyString(){ *str = '\0'; } //default constructor
MyString(char *s){ //parameterized constructor
str = s;
}
MyString(const MyString& s) {
strcpy_s(str, 10, s.str);
}
MyString& operator=(const MyString& s) {
strcpy_s(str, 10, s.str);
return *this;
}
private:
int length(char* s){
int i = 0;
while (s[i] != '\0')
i++;
return i;
}
char* delchar(char* s, int count, int start){
int i, j = 0;
char *temp = new char[10];
for (i = start; i<start + count; i++){
s[i] = ' ';
}
for (i = 0; i<length(s); i++){
if (s[i] != ' ')
temp[j++] = s[i];
}
temp[j] = '\0';
s = temp;
return s;
}
public:
MyString operator-(MyString s){
int i = 0, j = 0, count = 0, start = -1;/* i to iterate the first string,j to iterate the second string*/
MyString temp; /* count to count the matched characters ,start to know the starting index*/
temp.str = str;
while (temp.str[i] != '\0'){
j = 0;
start++;
while (s.str[j] != '\0'){
if (temp.str[i] == s.str[j]){
count++;
i++;
j++;
if (count == length(s.str)){//checks if the count
temp.str = delchar(temp.str, count, start);
i = i - count;
start = i - 1;
count = 0;
}
}
else{
i++;
count = 0;
break;
}
}
}
return temp;
}
~MyString(){
//delete str;
}
friend ostream &operator<<(ostream &stream, MyString& s){
stream << s.str << endl;
return stream;
}
};
int main(){
char *p = new char[20];
char *q = new char[10];
cin >> p;
cin >> q;
MyString s1(p);
MyString s2(q);
MyString s3;
s3 = s1 - s2;
cout << s3;
delete p;
delete q;
return 0;
}