With regard to this line:
The s[9] that goes through void setChar(size_t index, char c), but what is *this (inside the if, in the line: ``)?
Is it s1 or nullptr? Then what is _str (in the same line: *this = _str;), is it s1?
class MyString {
size_t _size = 0;
char* _str = nullptr;
int* _pRefCount = nullptr;
}
operator char() const {
return ((const MyString&)_s)[_index];
}
};
void detach() {
if(_pRefCount && --*_pRefCount == 0) {
delete []_str;
delete _pRefCount;
}
}
void attach(const MyString& str) {
_size = str._size;
_str = str._str;
_pRefCount = str._pRefCount;
++*_pRefCount;
As always *this is the object the function is executed for. If you are inside s1[4] the *this would be s1.
Using *this = _str; is just an involved way of calling one of the operator= overloads for MyString. It has the effect of "unsharing" by assigning a new copy of _str to itself.
Also, it has been known for quite some time that reference counted strings is not an optimization. For example in the output of s1[0] the code will create an unnecessary CharProxy just in case someone would assign a value to it. You also get a separate memory allocation for the refcount of each string you create, even if you never intend to share it
Related
I have a class that contains the operator + () to add 2 objects. The class contains an attribute char * variable str that points to an allocated char array from new char[] that contains a C style text string. I want the operator +() to concatenate two char arrays into a third char array allocated as a new buffer sized to contain the concatenated string as the result of the operator.
I am using overloaded constructor and initializing list and I initialize the variable to a default value. The destructor I've added to the class frees the memory allocated for the object text buffer in the char *str variable.
Everything works perfectly fine WITHOUT the class destructor, but as soon as I add the destructor, the program prints weird characters. I am also seeing a memory leak.
Here is the code and note that the class destructor is included here!
#include <iostream>
#include <cstring>
#include<cstring>
class Mystring {
private:
char *str;
public:
Mystring():
str{nullptr} {
str = new char[1];
*str = '\0';
}
Mystring(const char *s):
str {nullptr} {
if (s==nullptr) {
str = new char[1];
*str = '\0';
} else {
str = new char[std::strlen(s)+1];
std::strcpy(str, s);
}
}
Mystring(const Mystring &source):
str{nullptr} {
str = new char[std::strlen(source.str)+ 1];
std::strcpy(str, source.str);
std::cout << "Copy constructor used" << std::endl;
}
~Mystring(){
delete [] str;
}
void print() const{
std::cout << str << " : " << get_length() << std::endl;
}
int get_length() const{
return std::strlen(str);
}
const char *get_str() const{
return str;
}
Mystring&operator+(const Mystring &dstr)const;
};
// Concatenation
Mystring &Mystring::operator+(const Mystring &dstr)const {
char *buff = new char[std::strlen(str) + std::strlen(dstr.str) + 1];
std::strcpy(buff, str);
std::strcat(buff, dstr.str);
Mystring temp {buff};
delete [] buff;
return temp;
}
int main() {
Mystring m{"Kevin "};
Mystring m2{"is a stooge."};
Mystring m3;
m3=m+m2;
m3.print();
return 0;
}
Mystring &Mystring::operator+(const Mystring &dstr)const
This states that it's going to return a reference to a MyString Your temp however is local. This results in a far more serious issue than just a memory leak.
Once you're returning by value; you'll find that it might end up wanting to use the copy or move constructor; which leads to (as said in the comments) the rule of 3 (or rule of 5).
This
Mystring &Mystring::operator+(const Mystring &dstr)const {
char *buff = new char[std::strlen(str) + std::strlen(dstr.str) + 1];
std::strcpy(buff, str);
std::strcat(buff, dstr.str);
Mystring temp {buff};
delete [] buff;
return temp;
}
contains a bug. You create a temp object and return it by reference (Mystring&). That reference turns into a dangling one as soon as the function returns and using it is undefined behaviour. The fact that the code appears to work with or without the destructors means nothing. Undefined behaviour is undefined and you cannot expect anything from the code that invokes it.
You may simply change the return type of Mystring::operator+ to return by value (simply Mystring).
Although, you would encounter another problem later on.
m3=m+m2;
performs a copy assignment, which uses T& operator = (const T&). You did not provide one, so the compiler generated one.
Why is that bad? Because automatically generated special member functions perform shallow copies (if they are used for copying), which means that if you have, for example, pointer member variable, the pointer will be copied. Just the pointer. Not the data it points to. Just the pointer. You end up with two pointers pointing to the same memory and once a correctly defined destructor tries to free those resources, you encounter a double delete[] error, which, again, is undefined behaviour for non-nullptr pointers.
You may want to look forward into the rule of three, and then probably into the rule of five.
#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'm trying out a C# style string implementation in C++.
I have created an object and a pointer for class String, and assigned the object to the pointer. When i try to modify the object via the pointer instead of modifying the existing object i want to create a new object and make the pointer point it.
So i have overloaded the "=" operator, and creating a new object in the operator overloaded method. In order to reflect the change i need to use ss=*ss = "name";
Any suggestion to improve this code.
Below is my sample code
class String
{
char *str;
public:
String(char* str_in)
{
str = new char[strlen(str_in)];
strcpy(str, str_in);
}
String* operator=(char* s)
{
return new String(s);
}
};
int main()
{
String s="sample";
String *ss;
ss = &s;
ss=*ss = "name";
return 0;
}
I also tried to modify the this pointer, but not working as expected
String *ptr;
ptr = const_cast<String*>(this);
ptr = new String(s);
I would recommend some changes like this:
#include <string.h>
class String
{
char *str;
public:
String(const char* str_in)
{
str = new char[strlen(str_in)];
strcpy(str, str_in);
}
~String()
{
delete [] str;
}
String& operator=(const char* s)
{
char* tmp = new char[strlen(s)];
strcpy(tmp, s);
delete [] str;
str = tmp;
return *this;
}
};
int main()
{
String s("sample");
String *ss;
ss = &s;
ss = new String("name");
delete ss;
return 0;
}
First of all, you need an appropriate destructor or you are going to have a memory leak when String gets destroyed. Deleting the char* fixes this (since it is an array, we use the array delete).
Secondly, in C++, we almost always return a reference for operator= (not a pointer). So this revised operator= function is probably better - it deletes the old string, allocates memory for the new string, copies the new string, and returns *this.
Third, you can use const char* instead of char* for the constructor and assignment operator since you are not editing it.
In main(), I also created a new object for the pointer to point to since you requested that in the original post (and then it is deleted afterwards to avoid a memory leak).
Let me know if you have any questions with those changes.
I write a simple string class in cpp, but when I add some new features something bad happened. The stack overflow when to call the object s3 destructor. So I spent an hour to fix it, but I did not find something wrong with my code locially and the grammer is correct. So I ask my friend to recode it to help me to fix it, but when he recoded it on his Mac. There was no bad thing happened in his program. So I am REALLY REALLY confused about it and need help to fix it, if there is something wrong with my codes in truth. If not, maybe I should comment on issue Visual Studio 2015. :(
This is MySTLString.h
class MySTLString
{
public:
// Default constructor
MySTLString();
// Constructs the string with count copies of character ch.
MySTLString(size_t count, char ch);
// Convert char consequence to MySTLString
MySTLString(const char *s);
// Copy constructor
MySTLString(const MySTLString &s);
// Destructor
~MySTLString();
using size_type = size_t;
using CharT = char; // the character type
using value_type = char; // the value_type
using reference = value_type; // reference
using const_reference = const value_type&; // const reference
// Returns reference to the character at specified location pos.
reference at(size_type pos);
// const_reference at(size_type pos) const; // I do not know how to convert a reference to const_reference when to return
// Returns reference to the first character
CharT& front();
const CharT& front() const;
// Returns reference to the last character, equivalent to operator[](size() - 1)
CharT& back();
const CharT& back() const;
// Returns pointer to the underlying array serving as character storage.
CharT* data();
const CharT* data() const;
// Operator assignment overloaded
MySTLString& operator=(const MySTLString &s);
// Operator [] overloaded
reference operator[](size_type pos);
const_reference operator[](size_type pos) const;
private:
char* data_;
int length_;
};
This is MySTLString.cpp
#include "MySTLString.h"
#include <cstring>
#include <iostream>
#include <stdexcept>
// Construct a empty string(zero size and unspecified capacity).
MySTLString::MySTLString():data_(nullptr), length_(0)
{
}
// Constructs the string with count copies of character ch.
MySTLString::MySTLString(size_t count, char ch)
{
length_ = count;
if (count == 0)
{
data_ = nullptr;
return;
}
else // when count is not 0
{
data_ = new char[length_];
for (size_t i = 0; i < count; ++i)
{
data_[i] = ch;
}
}
}
// Constructs the string with contents initialized
// with a copy of the null-terminated character string pointed to by s.
// The length of the string is determined by the first null character.
MySTLString::MySTLString(const char *s)
{
length_ = strlen(s);
if (length_ == 0)
{
data_ = nullptr;
return;
}
data_ = new char[length_];
strcpy(data_, s);
}
// Copy constructor.
// Constructs the string with the copy of the contents of other.
MySTLString::MySTLString(const MySTLString &s)
{
if (s.data_ == nullptr)
{
length_ = 0;
data_ = nullptr;
}
else
{
length_ = strlen(s.data_);
data_ = new char[length_];
strcpy(data_, s.data_);
}
}
// Destructor
// Free data_ pointer memory
MySTLString::~MySTLString()
{
if (data_ != nullptr)
{
delete []data_;
}
std::cout << "length_ = " << length_ << std::endl; // for test
}
// Returns a reference to the character at specified location pos.
// Bounds checking is performed, exception of type std::out_of_range will be thrown on invalid acess
MySTLString::reference MySTLString::at(size_type pos)
{
if (pos >= strlen(data_))
{
throw std::out_of_range("pos is cross-border!\n");
}
return data_[pos];
}
// Returns reference to the first character
MySTLString::CharT& MySTLString::front()
{
if (data_ == nullptr)
{
throw std::out_of_range("String is empty!\n");
}
return data_[0];
}
const MySTLString::CharT& MySTLString::front() const
{
return this->front();
}
// Returns reference to the last character
MySTLString::CharT& MySTLString::back()
{
if (data_ == nullptr)
{
throw std::out_of_range("String is empty!\n");
}
return data_[0];
}
const MySTLString::CharT& MySTLString::back() const
{
return this->back();
}
// Returns pointer to the underlying array serving as character storage.
// The pointer is such that the range[data(); data()+strlen(data_)] is valid
// in it correspond to the values stored in the string
MySTLString::CharT* MySTLString::data()
{
if (data_ == nullptr)
{
throw std::out_of_range("String is empty!\n");
}
return data_;
}
const MySTLString::CharT* MySTLString::data() const
{
return this->data();
}
// Operator= overloaded
// Replace the contents with a copy of str.
// If *this and str are the same object, this function has no effect
MySTLString& MySTLString::operator=(const MySTLString &s)
{
// If *this and str are the same object, this function return *this
if (this == &s)
{
return *this;
}
if (s.length_ == 0)
{
length_ = 0;
data_ = nullptr;
return *this;
}
char* temp = s.data_; // copy *s.data_
delete data_; // free old memory
data_ = new char[s.length_]; // copy data to data_ member
strcpy(data_, temp);
length_ = s.length_;
return *this; // return this object
}
// Operator[] overloaded
// Returns a reference to the character at specified location pos.
// No bounds checking is perfromed.
MySTLString::reference MySTLString::operator[](size_type pos)
{
return this->at(pos);
}
MySTLString::const_reference MySTLString::operator[](size_type pos) const
{
return this->operator[](pos);
}
This is TestMySTLString.cpp(PS: I have delete other normal functions)
#include "MySTLString.h"
#include <iostream>
using std::cout;
using std::endl;
int main(void)
{
// test constructor that convert char consequence to MySTLString
MySTLString s3("qwe");
return 0;
}
This is the pic when to call the destructor
This is the pic of stack information when to call the destructor
const MySTLString::CharT& MySTLString::front() const
{
return this->front();
}
will result in infinite recursion, causing stack overflow. If you want to re-use implementaion of the non-const version, you will have to use:
const MySTLString::CharT& MySTLString::front() const
{
return const_cast<MySTLString*>(this)->front();
}
Make the same change to MySTLString::back() and MySTLString::data().
While you do have infinite recursive calls (as explained in those other answers) it isn't an answer to the question you are asking. The error you are getting is a heap corruption bug.
The debug CRT of Visual Studio allocates guard bytes to the left and right of heap-allocated memory, and fills them with specific byte patters. When the memory gets deleted, those guard bytes are compared against the byte pattern. If they do not match, you get to see the heap corruption dialog, because you wrote outside the allocated memory.
The bug is not in your d'tor. The d'tor is just the place, where the heap corruption is detected, because it releases the heap-allocated memory.
There are several bugs with the same pattern in your code: strlen returns the number of characters not including the zero terminator. When you strcpy into the allocated array, the zero terminator gets written just outside the allocated memory.
You need to change the following code
MySTLString::MySTLString(const char *s)
{
length_ = strlen(s);
if (length_ == 0)
{
data_ = nullptr;
return;
}
data_ = new char[length_];
strcpy(data_, s);
}
to
MySTLString::MySTLString(const char *s)
{
length_ = strlen(s);
if (length_ == 0)
{
data_ = nullptr;
return;
}
data_ = new char[length_ + 1]; // Allocate enough memory to account for zero terminator
strcpy(data_, s);
}
Make sure to update the other occurrences, where you call strlen accordingly.
Other random notes:
You don't need to check for nullptr before calling delete[]. While not harmful, it's not required either. Simply use delete[] data_; in your d'tor.
There were no error reports for your friend, because they were presumably using XCode. XCode will not report heap corruption errors, unless it is specifically set up to do so (the tedious process is explained at Enabling the Malloc Debugging Features). And even then it's mostly useless.
Have a look in your visual Studio 2015 Build output. Mine says
data': recursive on all control paths, function will cause runtime stack overflow
back': recursive on all control paths, function will cause runtime stack overflow
front': recursive on all control paths, function will cause runtime stack overflow
operator[]': recursive on all control paths, function will cause runtime stack overflow
The debugger is giving me 'bad ptr' when I create a new string array in this constructor, but only when my overloading operator method creates a new MyString object... confused.
Here is my constructor
MyString::MyString()
{
stringSize = 0;
stringCap = 16;
stringArray = new char[stringCap + 1];
stringArray[0] = '\0';
}
Here is my overloading operator method
MyString operator+(const char* leftOp, const MyString& rightOp)
{
MyString result; // new object used to store result
result.stringSize = strlen(leftOp) + rightOp.stringSize;
// if the string does not fit in the array
if( result.stringSize > result.stringCap )
{
delete[] result.stringArray;
result.stringCap = ( result.stringSize + 15 ) & ~15;
result.stringArray = new char[result.stringCap + 1];
}
strcpy(result.stringArray, leftOp);
strcat(result.stringArray, rightOp.stringArray);
return result;
}
Here is my copy constructor, which the debugger never gets too
MyString::MyString(const MyString& s)
{
stringSize = s.stringSize;
stringCap = s.stringCap;
//stringArray[stringCap + 1];
stringArray = new char[stringCap + 1];
stringArray = s.stringArray;
}
Well, when this method returns, "result" is going to be copied and the original destructed. If the destructor deletes the array, and there isn't a smart copy constructor which ensures that the new copy includes a valid new array, then you're going to have problems.
But you said the compiler says something about a bad pointer -- where? What line?
Since from your code snippet, nothing seems wrong, my sixth sense tells me that you've NOT written copy-constructor, and are working with the default one generated by the compiler, or possibly stringArray is not a null-terminated string!
EDIT:
In your copy-constructor, this is wrong:
stringArray = s.stringArray; //wrong!
Use strcpy instead:
strcpy(stringArray, s.stringArray); //correct!
Make sure all your strings are null-terminated!