I am trying to implement a basic string class in C++. However, I am stuck on the append() function. It would like it to be Hello World, but it results as Hello ÍWorlýýýý««««««««þ:
#define __START namespace lib{
#define __END }
__START
class string
{
public:
string(const char* s)
{
_str = s;
}
const char operator[](int position)
{
return _str[position];
}
void operator=(string s)
{
_str = s.c_str();
}
void append(string s)
{
size_t nlength = (length() + s.length());
char* nstr = new char[nlength];
for (int i = 0; i < length(); ++i)
{
nstr[i] = _str[i];
}
for (int i = length() + 1; i < nlength; ++i)
{
nstr[i] = s[(i - length() - 1)];
}
_str = const_cast<const char*>(nstr);
}
void operator+=(string s)
{
append(s);
}
const char* c_str()
{
return _str;
}
size_t length()
{
return strlen(_str);
}
std::string str()
{
return _str;
}
private:
const char* _str;
};
__END
int main()
{
lib::string s = "Hello ";
s.append("World"); // s += "World";
std::cout << s.c_str();
getchar();
}
There are a lot of errors, not only with append
string(const char* s)
{
_str = s;
}
The constructor is wrong, you should make a copy of s in order to free it later, this way:
~string()
{
delete[] _str; // or free(_str) in case you use malloc/realloc, thanks Fred!
}
Private member variable:
private:
const char* _str;
The internal string should not be const, you should be able to resize it later
const char operator[](int position)
{
return _str[position];
}
You are missing a check: length() > position
void operator=(string s)
{
_str = s.c_str();
}
You are not modifying s, it should be const string& s
You are also not copying s.c_str() which means that now s and this are sharing the same internal buffer
void append(string s) // s should be a const reference too
{
size_t nlength = (length() + s.length());
char* nstr = new char[nlength];
for (int i = 0; i < length(); ++i)
{
nstr[i] = _str[i];
}
for (int i = length() + 1; i < nlength; ++i)
{
nstr[i] = s[(i - length() - 1)];
}
_str = const_cast<const char*>(nstr);
}
Should be easier to write in terms of realloc:
void append(string s)
{
int start = length();
_str = realloc(_str, length() + s.length());
for (int i = 0; i < s.length(); i++) {
_str[start+i] = s[i];
}
}
If you want to stick to new its OK, but you must free _str before assigning it to the new one.
The following operators should be const:
const char* c_str() const;
size_t length() const;
std::string str();
Update: Options for the constructor:
// option one (use delete[] to cleanup _str)
string(const char* s) {
int n = strlen(s);
_str = new char[n+1];
memcpy(_str, s, n+1); // s is NULL terminated
}
// option two (use free() to cleanup _str)
string(const char* s) {
int n = strlen(s);
_str = (char*)malloc(n+1);
memcpy(_str, s, n+1); // s is NULL terminated
}
// option 3: rely on append taking a char* argument
string(const char *s) : _str(NULL) {
append(s, strlen(s));
}
..
void append(const string& s) {
append(s.c_str(), s.length())
}
void append(const char *s, int len) {
int start = _str ? length() : 0;
_str = realloc(_str, start + len);
for (int i = 0; i < len; i++) {
_str[start+i] = s[i];
}
}
Update 2: It will be better to use size_t or unsigned int instead of plain int because the size is always greater than or equal to zero.
There are tons of problems with your code, so let's do this step by step.
First, you don't need your preprocessor magic for the namespace, but just a basic namespace mynamespace{}.
Secondly, it is ideal to create a basic_string class, so that it can be used with different character types. char / wchar_t, e.t.c.
Problems with your class:
1) private: const char *_str;. The pointer will be modified, so the const is useless.
2) There are no typedefs. You need them if you are attempting to re-implement a STL class. (Explained in example)
3) Use an allocator. This way you can construct, and destroy elements, allocate, and deallocate memory. You will be certain to keep your memory a lot safer this way.
4) Strings must be null terminated. That means an extra '\0' at the end, meaning you will have to allocate an extra byte for that. Strings are null terminated, because it is a way of telling the code to stop reading the string.
5) You are assigning to a string which has not been allocated. _str = s.c_str(); could easily crash, depending on your compiler, because you are writing to non-allocated memory.
6) Use const references, rather than normal types for your parameters (string = const string &). You also need this for your copy constructor basic_string(const _Myt &).
I still may not have highlighted all of the problems
Example basic_string class
template < typename _Elem, typename _Traits = std::char_traits<_Elem>, typename _Alloc = std::allocator<_Elem> > class basic_string
{
public:
typedef basic_string<_Elem, _Traits, _Alloc> _Myt;
typedef _Elem value_type;
typedef _Traits traits_type;
typedef _Alloc allocator_type;
typedef value_type *pointer;
typedef const value_type *const_pointer;
typedef value_type *iterator;
typedef const value_type *const_iterator;
typedef value_type &reference;
typedef const value_type &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
basic_string()
{
__data = _Alloc().allocate(1);
_Alloc().construct(&__data[0], '\0');
}
basic_string(const_pointer _Init)
{
int count = 0;
__data = _Alloc().allocate(_Traits::length(_Init) + 1);
for (const_iterator i = &_Init[0]; i != &_Init[_Traits::length(_Init)]; ++i, ++count)
{
_Alloc().construct(&__data[count], *i);
}
_Alloc().construct(&__data[_Traits::length(_Init)], '\0');
}
basic_string(const _Myt &_Init)
{
if (this != &_Init)
{
int count = 0;
__data = _Alloc().allocate(_Traits::length(_Init.__data) + 1);
for (const_iterator i = &_Init.__data[0]; i != &_Init.__data[_Traits::length(_Init.__data)]; ++i, ++count)
{
_Alloc().construct(&__data[count], *i);
}
_Alloc().construct(&__data[_Traits::length(_Init.__data)], '\0');
}
else
{
__data = _Alloc().allocate(1);
_Alloc().construct(&__data[0], '\0');
}
}
~basic_string()
{
if (__data)
{
size_type tmp = size();
for (iterator i = begin(); i != end(); ++i)
{
_Alloc().destroy(i);
}
_Alloc().deallocate(__data, tmp);
}
}
_Myt &assign(const_pointer _Rhs)
{
int count = 0;
reserve(_Traits::length(_Rhs) + 1);
for (const_iterator i = &_Rhs[0]; i != &_Rhs[_Traits::length(_Rhs)]; ++i, ++count)
{
_Alloc().construct(&__data[count], *i);
}
_Alloc().construct(&__data[_Traits::length(_Rhs)], '\0');
return *this;
}
_Myt &operator=(const_pointer _Rhs)
{
return assign(_Rhs);
}
_Myt &append(const_pointer _Rhs)
{
int count = size();
reserve(size() + _Traits::length(_Rhs) + 1);
for (const_iterator i = &_Rhs[0]; i != &_Rhs[_Traits::length(_Rhs)]; ++i, ++count)
{
_Alloc().construct(&__data[count], *i);
}
_Alloc().construct(&__data[count], '\0');
return *this;
}
_Myt &operator+=(const_pointer _Rhs)
{
return append(_Rhs);
}
iterator begin()
{
return &__data[0];
}
iterator end()
{
return &__data[size()];
}
size_type size()
{
return _Traits::length(__data);
}
_Myt &swap(basic_string<_Elem> &_Rhs)
{
std::swap(__data, _Rhs.__data);
return *this;
}
void reserve(size_type _Size)
{
int count = 0;
if (_Size < size())
{
return;
}
pointer buf = _Alloc().allocate(_Size);
for (iterator i = begin(); i != end(); ++i, ++count)
{
_Alloc().construct(&buf[count], *i);
}
std::swap(__data, buf);
for (iterator i = &buf[0]; i != &buf[_Traits::length(buf)]; ++i)
{
_Alloc().destroy(i);
}
_Alloc().deallocate(buf, _Traits::length(buf));
}
operator const_pointer()
{
return __data;
}
operator pointer()
{
return __data;
}
template < typename _Traits1, typename _Alloc1 > friend std::basic_ostream<_Elem> &operator<<(std::basic_ostream<_Elem> &_Stream, basic_string<_Elem, _Traits1, _Alloc1> &_Str)
{
return _Stream << _Str.c_str();
}
const_pointer data() const
{
return __data;
}
const_pointer c_str() const
{
return __data;
}
private:
pointer __data;
};
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
Of course, there is still a bit missing, but I'm sure you can implement that with a little online help.
First of all, your most critical problem is in char* nstr = new char[nlength];.
You must change it to char* nstr = new char[nlength+1];.
Then, in function append, after the two for loops, set nstr[nlength] = 0;
Second, for better performance (as well as correct coding), you probably need to change string s to const string& s in the following functions:
void operator=(string s)
void append(string s)
void operator+=(string s)
You have an off-by-one error in the second loop; the second string needs to be copied to length(), immediately after the end of the first:
for (int i = length(); i < nlength; ++i)
{
nstr[i] = s[i - length()];
}
You'll also need to allocate one more byte to put a null terminator on the end.
Note that you don't need that scary-looking cast to add const to the pointer, since that's a perfectly safe thing to do. const_cast is only needed to remove qualifiers. You might also want to fix the memory leak and, for bonus points, cache the length so you don't have to read the entire string every time you want it.
for (int i = length() + 1; i < nlength; ++i)
{
nstr[i] = s[(i - length() - 1)];
}
You first copy chars up to length()-1 and then start back at length() + 1, so you skip a char and there could be anything in there after allocation(unless you do a memset beforehand).
Then you need to terminate your string with a null character(\0) so that the std::string that gets constructed on str() method return knows when to stop reading chars.
Related
I am trying to write a basic string class for practice, and I am struggling with the reserve() function to increase my char array's memory allocation.
This is the error message I get:
free(): double free detected in tcache 2
This is my approach:
void string::reserve(size_t n)
{
if (n <= space+1) return; // never decrease allocation
char *p = new char[n+1]; // allocate new space
for (int i=0; i<sz; ++i) p[i]=pChars[i]; // copy old elements
//strcpy(p, pChars); // didn't work either
p[n] = '\0';
delete[] pChars; // deallocate old space
pChars = p;
space = n+1;
}
I will also share a picture of my header file below:
Here is the entire code:
#include "String.h"
namespace String
{
string::string(): sz(0), pChars(nullptr), space(0) {}
string::string(const char* s): sz(strlen(s)), pChars(new char[strlen(s)+1]), space(sz+1) ///Constructor using a null-terminated C String
{
//for (int i = 0; s[i] != '\0'; i++) pChars[i] = s[i];
strcpy(pChars,s);
pChars[strlen(s)] = '\0';
}
string::string(const string& s): sz(s.sz), pChars(new char[s.sz]), space(s.sz) ///copy constructor
{
for (size_t i = 0 ; i<s.sz; i++) pChars[i] = s.pChars[i];
pChars[s.sz] = '\0';
}
string& string::operator=(const string& s) ///copy assignment
{
if (this==&s) return *this; // self-assignment, no work needed
if (s.sz<=space)
{ // enough space, no need for new allocation
for (size_t i = 0; i<s.sz; ++i) pChars[i] = s.pChars[i]; // copy elements
sz = s.sz;
return *this;
}
char *p = new char [s.sz]; // copy and swap
for (size_t i = 0; i<s.sz; ++i) p[i] = s.pChars[i];
delete[] pChars;
sz = s.sz;
space = s.sz;
pChars = p;
return *this;
}
string& string:: operator=(const char* s) ///copy assignment using a null-terminated C String
{
char *p = new char[strlen(s)+1];
for(size_t i = 0; s[i] != '\0'; i++) p[i] = s[i];
p[sz] = '\0';
delete [] pChars;
sz = strlen(s);
space = sz;
strcpy(pChars, p);
return *this;
}
///string(string&& s); ///move constructor, not needed (compiler copy elision)
string& string:: operator=(string&& s) ///move assignment
{
delete[] pChars; // deallocate old space
pChars = s.pChars; // copy a's elem and sz
sz = s.sz;
space = s.space;
s.pChars = nullptr; // make a the empty vector
s.sz = 0;
cout << "Move" << endl;
return *this;
}
string::~string()
{
delete [] pChars;
}
char& string:: operator[](size_t pos) {return pChars[pos];} ///set operator
const char& string:: operator[](size_t pos) const {return pChars[pos];} ///get operator
void string::reserve(size_t n)
{
if (n <= space+1) return; // never decrease allocation
char *p = new char[n+1]; // allocate new space
for (int i=0; i<sz; ++i) p[i]=pChars[i]; // copy old elements
//strcpy(p, pChars);
p[sz] = '\0';
delete[] pChars; // deallocate old space
pChars = p;
space = n+1;
}
void string:: push_back(const char c) ///push char c to the end
{
if (sz==0) // no space: grab some
reserve(8);
else if (sz==space) // no more free space: get more space
reserve(2*space);
pChars[sz] = c; // add d at end
++sz; // and increase the size (sz is the number of elements)
}
bool operator==(const string& lhs, const string& rhs) ///check equality (same size, all chars the same)
{
if (lhs.size() != rhs.size()) return false;
else
{
for (size_t i = 0; i<lhs.size(); i++)
{
if (lhs[i] != rhs[i]) return false;
}
}
return true;
}
bool operator==(const string& lhs, const char* rhs) ///equality with null terminated C string
{
if(lhs.size() == strlen(rhs))
{
for (size_t i = 0; i< lhs.size(); i++)
{
if(lhs[i] == rhs[i]) return true;
}
}
return false;
}
bool operator>(const string& lhs, const string& rhs) ///greater than operator, true if lhs > rhs
{
}
string operator+(const string& lhs, const string& rhs) ///concatenate two MyStrings, return the result
{
}
ostream& operator<<(ostream& os, const string& s)
{
for (size_t i = 0; i<s.size(); i++)
{
os << s[i];
}
return os;
}
istream& operator>>(istream& is, string& s)
{
char c;
while(is.get(c))
{
if (c!=' ') s.push_back(c);
}
return is;
}
}
First error
string::string(const string& s): sz(s.sz), pChars(new char[s.sz]), space(s.sz) ///copy constructor
{
for (size_t i = 0 ; i<s.sz; i++) pChars[i] = s.pChars[i];
pChars[s.sz] = '\0';
}
This allocates an array of s.sz chars and then writes off the end of it
SHould be
string::string(const string& s): sz(s.sz), pChars(new char[s.sz] + 1), space(s.sz) ///copy constructor
{
for (size_t i = 0 ; i<s.sz; i++) pChars[i] = s.pChars[i];
pChars[s.sz] = '\0';
}
I have a problem about initializing char* a = new char[size]. Here is my code.
class Practice
{
public:
Practice(const char* a);
~Practice();
const char* getString() const;
private:
char* mString;
int mSize;
};
#include "Practice.h"
Practice::Practice(const char * a)
:mSize(0)
,mString(nullptr)
{
while (a[mSize] != '\0')
{
mSize++;
}
mString = new char[mSize];
for (int i = 0; i < mSize; i++)
{
mString[i] = a[i];
}
}
Practice::~Practice()
{
delete[] mString;
}
const char* Practice::getString() const
{
return mString;
}
int main()
{
Practice p("Hello");
std::cout << p.getString() << std::endl;
return 0;
}
I expected the result is Hello.
But the result was like Hello²²²²▌▌▌▌▌▌▌■a%{▌.
I thought I initialized the mString member variable through mString = new char[mSize]. But it was not working the way I thought.
Can anybody enlighten me what's wrong with my code and fix it?
mString is not NUL-terminated.
Your're checking a constructor parameter for \0. But you are not allocating mString to put the same \0 there, and don't copy \0 from a.
So, resulting mString is not properly NULL-terminated and it would be read beyond end.
Reading beyond end of allocated is undefined behavior. In particular case, it is likely that either mString would output until some accidental zero, or crash would occur.
while (a[mSize] != '\0')
{
mSize++;
}
This sets mSize equal to the length of a, excluding the terminating '\0'. You should include the terminator in your copy:
while (a[mSize++])
{
}
or simply:
mSize = strlen(a) + 1;
You are not null-terminating your mString data, but it is expecting to be null-terminated when you pass it to std::cout. Without that terminator, std::cout reads into surrounding memory until it encounters a random null byte (or crashes with a read access error). That is why you are seeing std::cout output random garbage after your data.
You are also not following the Rule of 3/5/0, as you are missing a default constructor, copy and move constructors, and copy and move assignment operators.
Try this:
class Practice
{
public:
Practice(const char* a = nullptr);
Practice(const Practice &src);
Practice(Practice &&src);
~Practice();
Practice& operator=(Practice src);
const char* getString() const;
private:
char* mString;
int mSize;
};
#include "Practice.h"
#include <utility>
Practice::Practice(const char * a)
: mSize(0)
, mString(nullptr)
{
if (a)
{
while (a[mSize] != '\0')
{
++mSize;
}
}
mString = new char[mSize + 1];
for (int i = 0; i < mSize; ++i)
{
mString[i] = a[i];
}
mString[mSize] = '\0';
}
Practice::Practice(const Practice &src)
: mSize(src.mSize)
, mString(nullptr)
{
mString = new char[mSize + 1];
for (int i = 0; i < mSize; ++i)
{
mString[i] = src.mString[i];
}
mString[mSize] = '\0';
}
Practice::Practice(Practice &&src)
: mSize(src.mSize)
, mString(src.mString)
{
src.mString = nullptr;
src.mSize = 0;
}
Practice::~Practice()
{
delete[] mString;
}
Practice& Practice::operator=(Practice src)
{
std::swap(mString, src.mString);
std::swap(mSize, src.mSize);
return *this;
}
const char* Practice::getString() const
{
return mString;
}
#include <iostream>
#include "Practice.h"
int main()
{
Practice p("Hello");
std::cout << p.getString() << std::endl;
return 0;
}
I build a simple string class.
I try to make the concatenation functions which one of them is + and the other +=.
When trying to implement += I generate a Str object under that equals the 1st string and which size is s.size(). But then when I tried to append to it a new string t I need to free the old array s string and allocate new size for it. After the destructor of the temp Str object is called it stucks there when freeing the old space and I can't understand why. How can I deallocate the Str under the + member function?
class Str
{
public:
typedef size_t size_type;
typedef char* iterator;
typedef const char* const_iterator;
iterator begin(){ return p; }
iterator end() { return p + std::strlen(p); }
const_iterator begin() const { return p; }
const_iterator end() const { return p + std::strlen(p); }
size_type size() const { return data_length; }
Str() {};
Str(const Str& s):
p(new char[s.size() +1]),
data_length(s.size())
{
std::copy(s.begin(), s.end(), p);
p[data_length] = '\0';
}
Str(const char* cp) :
p(new char[std::strlen(cp) + 1 ]),
data_length(std::strlen(cp))
{
std::copy(cp, cp+ std::strlen(cp) + 1,p);//copies also the '\0' char to the last place in p
}
Str& operator=(Str& rhs)//assignment operator
{
if (&rhs != this)
{
uncreate();
create(rhs.size());
std::copy(rhs.begin(), rhs.end() + 1, p);
//p[rhs.size()] = '\0';
}
return *this;
}
Str& operator=(const char* cp)//assignment operator
{
if (cp!= p)
{
uncreate();
create(std::strlen(cp));
std::copy(cp, cp+std::strlen(cp), p);
p[data_length] = '\0';
}
return *this;
}
Str& operator+=(const Str&);
~Str()
{
delete[] p;//stucked here while returning from + member function
data_length = 0;
}
const char* c_str() const;
void copy(char* ,size_type);
private:
char* p;
size_type data_length = 0;
const_iterator ci() const { return p; }
void uncreate();
void create(size_type);
};
Str operator+(const Str& s, const Str& t)
{
Str r = s;
r += t;
return r;
}
inline Str& Str::operator+=(const Str &s)
{
//trying to allocate new space for this object
std::copy(s.begin(),s.end(),p+this->size());
p[data_length] = '\0';
return *this;
}
void Str::create(Str::size_type n)
{
p = new char[n + 1];
data_length = n;
}
void Str::uncreate()
{
delete[] p;//to check that p is allocated right
data_length = 0;
}
The main for example:
int main()
{
Str s1 = "hello";
Str s2 = "worly";
Str s3 = s1 + s2;
return 0;
}
I suppose you want something like this:
inline Str& Str::operator+=(const Str &s)
{
const int new_data_length = data_length + s.data_length;
char * temp = new char[new_data_length + 1];
memcpy(temp, p, data_length);
memcpy(temp + data_length, s.p, s.data_length);
delete [] p;
p = temp;
data_length = new_data_length;
p[data_length] = 0;
return *this;
}
Basically what the title says. I have been trying to write my own string class using only char arrays and, while my code works when I run it in Visual Studio, I have trouble with it when using gcc .The problem seems to be coming when i try to delete in my getData function(can be seen below) The exception I get is:
Exception thrown at 0x6262436B (ucrtbased.dll) in string.exe: 0xC0000005: Access violation reading location 0xCCCCCCBC. occurred
My code :
Header:
#pragma warning(disable:4996)
#ifndef STRING_STRING_H
#define STRING_STRING_H
#include<iostream>
#include<cstring>
#include<fstream>
class String {
private:
char *data; //holds the text
size_t maxSize; //maximum number of chars in data
size_t currentSize; //current number of chars in data
void getData(const char *, size_t maxSize); //sets currentSize to the other char* size and
// copies the content of the other char* to data
public:
String(); //default constructor
~String(); //destructor
String(const String &); //copy-constructor(from String)
String(const char *); //copy-constructor(from char*)
String operator=(const String &); //operator= (from string)
String operator=(const char *); //operator=(from char*)
size_t length() const; //currentSize getter
void addChar(const char); //adds a char to the data array
void getLine(std::ifstream&,const char); // reads line till deliminator and stores it in this string object(all data previously stored is lost)
size_t find(const char*); //searches for text in the string and if found returns the starting position , if not found returns -1;
void print() const; //prints the string object to console
char* toChar() const; //returns a new allocated char pointer with the text inside (must be deleted afterwards)
};
#endif //STRING_STRING_H
cpp:
#include "String.h"
String::String() {
currentSize = 0;
maxSize = 16;
try {
data = new char[maxSize];
data[0] = '\0';
}
catch (std::bad_alloc &) {
std::cerr << "Not enough memory" << std::endl;
throw;
}
}
String::~String() {
delete[] data;
}
size_t String::length() const {
return currentSize;
}
String::String(const String &other) {
this->maxSize = other.maxSize;
getData(other.data, maxSize);
}
String::String(const char *other) {
this->maxSize = strlen(other) *2;
getData(other, maxSize);
}
void String::getData(const char *dataSource, size_t maxSize) {
currentSize = strlen(dataSource);
try {
char *newData = new char[maxSize];
delete[] data;
data = newData;
strcpy(data, dataSource);
}
catch (std::bad_alloc &) {
std::cerr << "Not enough memory" << std::endl;
throw;
}
}
String String::operator=(const String &other) {
if (this != &other) {
maxSize = other.maxSize;
getData(other.data, maxSize);
}
return *this;
}
String String::operator=(const char *other) {
if (this->data != other) {
maxSize = strlen(other) *2;
getData(other, maxSize);
}
return *this;
}
void String::addChar(const char newChar) {
if (maxSize == currentSize+1) {
maxSize *= 2;
getData(this->data, maxSize);
}
data[currentSize++] = newChar;
}
void String::getLine(std::ifstream & is, const char delim='\n')
{
char temp;
while (!is.eof())
{
is.get(temp);
if (temp == delim)
break;
else
addChar(temp);
}
return;
}
size_t String::find(const char * text)
{
size_t currPos=-1;
bool found = 0;
for (size_t i = 0; i < currentSize; i++)
{
if (data[i] == text[0])
{
for (size_t j = i+1; j < currentSize; j++)
{
if (data[j] == text[j - i])
found = 1;
else
{
found = 0;
break;
}
}
if (found == 1)
{
currPos = i;
break;
}
}
}
return currPos;
}
void String::print() const
{
for (size_t i = 0; i < currentSize; i++)
{
std::cout << data[i];
}
std::cout << std::endl;
}
char * String::toChar() const
{
char* text= new char[currentSize+1];
for (size_t i = 0; i < currentSize; i++)
{
text[i] = data[i];
}
text[currentSize + 1] = 0;
return text;
}
Your problem is caused by calling delete [] on uninitialized memory.
In the copy constructor, the data member is not initialized before calling getData(). In getData(), you are using delete [] data;.
You could initialize data to nullptr in the constructors to avoid the problem.
It's always a good idea to initialize all variables to some sensible value before the body of the constructor. E.g. you can implement the copy constructor as:
String::String(const String &other) : currentSize(0),
maxSize(other.maxSize),
data(nullptr)
{
getData(other.data, maxSize);
}
Similarly, implement the constructor from char const* as:
String::String(const char *other) : currentSize(0),
maxSize(strlen(other) *2),
data(nullptr)
{
getData(other, maxSize);
}
I have a technical problem and it's really confusing me. I apologise in advance because I may not be giving the relevant details; I don't yet why it's going wrong and it would be excessive to include all the code I'm working with.
I'm working with a large program that uses the C++ STL. I'm moving this code to a very sensitive environment without a standard clib nor STL implementaton; it will redefine malloc/free/new/delete etc... For that, I need to replace the std:: parts with my own simplified implementations. I've started with std::vector. Right now it's running in the standard ecosystem so it's the GNU libc and STL. The only thing that's changed is this vector class.
When I execute the program with the replaced class, it segfaults. I've put this through GDB and found that the program will request an object from the vector using the subscript operator. When the object reference is returned, a method is invoked and the program segfaults. It seems it can't find this method and ends up in main_arena() in GDB. The type of the object is an inherited class.
I'm really not sure at all what the problem is here. I would love to provide additional details, but I'm not sure what more I can give. I can only assume something is wrong with my vector implementation because nothing else in the program has been changed. Maybe there's something obvious that I'm doing wrong here that I'm not seeing at all.
I'm using: g++ (GCC) 4.4.5 20110214 (Red Hat 4.4.5-6)
I'd really appreciate any feedback/advice!
#ifndef _MYSTL_VECTOR_H_
#define _MYSTL_VECTOR_H_
#include <stdlib.h>
#include <assert.h>
typedef unsigned int uint;
namespace mystl
{
/******************
VECTOR
********************/
template <typename T>
class vector
{
private:
uint _size;
uint _reserved;
T *storage;
void init_vector(uint reserve)
{
if (reserve == 0)
{
_reserved = 0;
return;
}
storage = (T*)malloc(sizeof(T)*reserve);
assert(storage);
_reserved = reserve;
}
public:
vector()
{
// std::cerr << "default constructor " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
}
vector(const vector<T> &other)
{
// std::cerr << "copy constructor " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(other.size());
_size = other.size();
for (uint i=0; i<other.size(); i++)
{
storage[i] = T(other[i]);
}
}
vector(uint init_num, const T& init_value)
{
// std::cerr << "special constructor1 " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(init_num);
for (size_t i=0; i<init_num; i++)
{
push_back(init_value);
}
}
vector(uint init_num)
{
// std::cerr << "special constructor2 " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(init_num);
}
void reserve(uint new_size)
{
if (new_size > _reserved)
{
storage = (T*)realloc(storage, sizeof(T)*new_size);
assert(storage);
_reserved = new_size;
}
}
void push_back(const T &item)
{
if (_size >= _reserved)
{
if (_reserved == 0) _reserved=1;
reserve(_reserved*2);
}
storage[_size] = T(item);
_size++;
}
uint size() const
{
return _size;
}
~vector()
{
if (_reserved)
{
free(storage);
storage = NULL;
_reserved = 0;
_size = 0;
}
}
// this is for read only
const T& operator[] (unsigned i) const
{
// do bounds check...
if (i >= _size || i < 0)
{
assert(false);
}
return storage[i];
}
T& operator[] (unsigned i)
{
// do bounds check...
if (i >= _size || i < 0)
{
assert(false);
}
return storage[i];
}
// overload = operator
const vector<T>& operator= (const vector<T>& x)
{
// check for self
if (this != &x)
{
_reserved = 0;
_size = 0;
storage = NULL;
init_vector( x.size() );
for(uint i=0; i<x.size(); i++)
{
storage[i] = T(x[i]);
}
_size = x.size();
}
return *this;
}
uint begin() const
{
return 0;
}
void insert(uint pos, const T& value)
{
push_back(value);
if (size() == 1)
{
return;
}
for (size_t i=size()-2; i>=pos&& i>=0 ; i--)
{
storage[i+1] = storage[i];
}
storage[pos] = value;
}
void erase(uint erase_index)
{
if (erase_index >= _size)
{
return;
}
//scoot everyone down by one
for (uint i=erase_index; i<_size; i++)
{
storage[i] = storage[i+1];
}
_size--;
}
void erase(uint start, uint end)
{
if (start > end)
{
assert(false);
}
if (end > _size)
end = _size;
for (uint i=start; i<end; i++)
{
erase(start);
}
assert(false);
}
void clear()
{
erase(0,_size);
}
bool empty() const
{
return _size == 0;
}
}; //class vector
}
#endif // _MYSTL_VECTOR_H_
Wow!
Your assignment operator also leaks memory.
Becuause you are using malloc/release the constructor to your type T will will not be called and thus you can not use your vector for anything except the most trivial of objects.
Edit:
I am bit bored this morning: Try this
#include <stdlib.h> // For NULL
#include <new> // Because you need placement new
// Because you are avoiding std::
// An implementation of swap
template<typename T>
void swap(T& lhs,T& rhs)
{
T tmp = lhs;
lhs = rhs;
rhs = tmp;
}
template <typename T>
class vector
{
private:
unsigned int dataSize;
unsigned int reserved;
T* data;
public:
~vector()
{
for(unsigned int loop = 0; loop < dataSize; ++loop)
{
// Because we use placement new we must explicitly destroy all members.
data[loop].~T();
}
free(data);
}
vector()
: dataSize(0)
, reserved(10)
, data(NULL)
{
reserve(reserved);
}
vector(const vector<T> &other)
: dataSize(0)
, reserved(other.dataSize)
, data(NULL)
{
reserve(reserved);
dataSize = reserved;
for(unsigned int loop;loop < dataSize;++loop)
{
// Because we are using malloc/free
// We need to use placement new to add items to the data
// This way they are constructed in place
new (&data[loop]) T(other.data[loop]);
}
}
vector(unsigned int init_num)
: dataSize(0)
, reserved(init_num)
, data(NULL)
{
reserve(reserved);
dataSize = reserved;
for(unsigned int loop;loop < dataSize;++loop)
{
// See above
new (&data[loop]) T();
}
}
const vector<T>& operator= (vector<T> x)
{
// use copy and swap idiom.
// Note the pass by value to initiate the copy
swap(dataSize, x.dataSize);
swap(reserved, x.rserved);
swap(data, x.data);
return *this;
}
void reserve(unsigned int new_size)
{
if (new_size < reserved)
{ return;
}
T* newData = (T*)malloc(sizeof(T) * new_size);
if (!newData)
{ throw int(2);
}
for(unsigned int loop = 0; loop < dataSize; ++loop)
{
// Use placement new to copy the data
new (&newData[loop]) T(data[loop]);
}
swap(data, newData);
reserved = new_size;
for(unsigned int loop = 0; loop < dataSize; ++loop)
{
// Call the destructor on old data before freeing the container.
// Remember we just did a swap.
newData[loop].~T();
}
free(newData);
}
void push_back(const T &item)
{
if (dataSize == reserved)
{
reserve(reserved * 2);
}
// Place the item in the container
new (&data[dataSize++]) T(item);
}
unsigned int size() const {return dataSize;}
bool empty() const {return dataSize == 0;}
// Operator[] should NOT check the value of i
// Add a method called at() that does check i
const T& operator[] (unsigned i) const {return data[i];}
T& operator[] (unsigned i) {return data[i];}
void insert(unsigned int pos, const T& value)
{
if (pos >= dataSize) { throw int(1);}
if (dataSize == reserved)
{
reserve(reserved * 2);
}
// Move the last item (which needs to be constructed correctly)
if (dataSize != 0)
{
new (&data[dataSize]) T(data[dataSize-1]);
}
for(unsigned int loop = dataSize - 1; loop > pos; --loop)
{
data[loop] = data[loop-1];
}
++dataSize;
// All items have been moved up.
// Put value in its place
data[pos] = value;
}
void clear() { erase(0, dataSize);}
void erase(unsigned int erase_index) { erase(erase_index,erase_index+1);}
void erase(unsigned int start, unsigned int end) /* end NOT inclusive so => [start, end) */
{
if (end > dataSize)
{ end = dataSize;
}
if (start > end)
{ start = end;
}
unsigned int dst = start;
unsigned int src = end;
for(;(src < dataSize) && (dst < end);++dst, ++src)
{
// Move Elements down;
data[dst] = data[src];
}
unsigned int count = start - end;
for(;count != 0; --count)
{
// Remove old Elements
--dataSize;
// Remember we need to manually call the destructor
data[dataSize].~T();
}
}
unsigned int begin() const {return 0;}
}; //class vector
With your current memory handling, this vector would only work with plain old data types.
To handle all types, it must ensure that objects
are actually created (malloc doesn't do that),
destroyed (free doesn't do that),
and you can't reallocate memory with realloc, because complex objects are not guaranteed to remain valid if they are byte-wise copied to another location.
Looks like the answer can be found in your question: "When the object reference is returned, a method is invoked and the program segfaults. It seems it can't find this method and ends up in main_arena() in GDB. The type of the object is an inherited class."
You probably store base class instance T in the vector, but make push_back for the instance of the class inherited from T. In push_back {storage[_size] = T(item);} you cast (actually make copy constructor T:T(const T&)) item to T (this probably named 'type cut'), then get reference to T and invoke a method of the class inherited from T using virtual table of T where the method is not defined yet/abstract. Am I right?
To make it properly work you should put T* in the vector or shared_ptr/unique_ptr depending on the ownership terms you apply to vector elements.
Generally in vector you can store only POD (Plain Old Data) types.