I'm trying to re-learn C++ and was wondering if anyone could help me out here. I'm trying to implement my own String class to see if I can remember things, but I'm stuck on the constructor.
I have my header file and want to have a constructor as so:
Header File (MyFiles\String.h):
#ifndef STRING_
#define STRING_
using namespace std;
#include <iostream>
class String
{
private:
static const unsigned int MAX = 32; // Capacity of string
char Mem[MAX]; // Memory to hold characters in string
unsigned Len; // Number of characters in string
public:
// Construct empty string
//
String()
{
Len = 0;
}
// Reset string to empty
//
void reset()
{
Len = 0;
}
// Return status information
//
bool empty() const
{
return Len == 0;
}
unsigned length() const
{
return Len;
}
// Return reference to element I
//
char& operator[]( unsigned I )
{
return Mem[I];
}
// Return constant reference to element I
//
const char& operator[]( unsigned I ) const
{
return Mem[I];
}
// Construct string by copying existing string
//
String( const String& );
// Construct string by copying array of characters
//
String( const char [] );
// Copy string to the current string
//
String& operator=( const String& );
// Append string to the current string
//
String& operator+=( const String& );
};
// Compare two strings
//
bool operator==( const String&, const String& );
bool operator!=( const String&, const String& );
// Put a string into an output stream
//
ostream& operator<<( ostream&, const String& );
#endif
The bit I'm stuck on is this:
String::String(const String& str)
{
//what goes here?
}
Thanks!
Well, since it's a learning exercise.
I think you want to copy the contents of the other string here since this is a copy constructor. So you will want to copy across all the member variables. In your case
the copy constructor is not necessary because you've got a static array. If you had
dynamic memory (i.e. used new to allocate pointer to Mem) then you'd need this. However,
to show you how it's done, here you go.
String::String(const String& str)
{
//what goes here?
assert(str.Len < MAX); // Hope this doesn't happen.
memcpy(Mem, str.Mem, str.Len);
Len = str.Len;
}
You need to copy the data from str to this. The length is easy:
Len = str.Len; // or, equiv. this->Len= str.Len
The data is a little harder. You might use strcpy or memcpy, or even a for loop.
memcpy(Mem, str.Mem, sizeof Mem);
Good luck!
I concur with Kornel Kisielewicz: the fewer hand-rolled String classes, the better. But you're only doing this to learn, so fair enough :-). Anyway: your copy constructor needs to copy over the length and the contents of the Mem array, and that's it.
(If you were doing this to make something useful rather than as a learning exercise, I'd add: a string class with a fixed maximum string length -- especially one as small as 32 characters -- is a very bad idea indeed. But it's entirely reasonable if you don't feel like dealing with memory allocation and deallocation at the same time as you're trying to remember the even-more-basics...)
Related
Below I have a bit of code for implementing Huffman compression.
What I was curious about was if I could initialize the left and right pointers without including cstdlib, or rather, if I could initialize an empty memory location for storing the left and right without using malloc.
Also, in my combine function, I do not want to use "NULL" for the string parent node of my left and right, but would rather have an empty string. Will I have to make a new constructor for this? I get an error (basic_string::_M_construct null not valid) when I replace "NULL" with nullptr.
#include <string>
#include <cstdlib>
#ifndef PRIORITY_NODE
#define PRIORITY_NODE
namespace Huffman
{
class PriorityNode
{
private:
std::string key; // The character sequence to compress
long frequency = 0; // The frequency of the character sequence
PriorityNode* left = (PriorityNode*)malloc(sizeof(PriorityNode));
PriorityNode* right = (PriorityNode*)malloc(sizeof(PriorityNode));
public:
PriorityNode(std::string k, long f): frequency(f), key(k){};
std::string getKey() const{ return key;}
long getFrequency() const{ return frequency;}
void setLeft(const PriorityNode& left){*this->left = left;}
void setRight(const PriorityNode& right){*this->right = right;}
PriorityNode& getLeft() const{ return *left;}
PriorityNode& getRight() const{ return *right;}
friend PriorityNode combine(const PriorityNode& lhs, const PriorityNode& rhs)
{
long ret_freq = lhs.getFrequency() + rhs.getFrequency();
PriorityNode ret = PriorityNode("NULL", ret_freq);
ret.setLeft(lhs);
ret.setRight(rhs);
return ret;
};
};
}
#endif
So a couple of points.
key is a string, not a pointer. It makes no sense to set it to nullptr, i am guessing "NULL", is just a stand in for when the key has no value. Instead of this, just use an empty string "".
You should try to avoid this manual memory management for several reasons. First of all you have no destructor, so your memory that you malloc is never freeed meaning you have a memory leak. Secondly, you allocate memory for the sub nodes, even if you do not need to. I would suggest something more like the following:
class PriorityNode {
private:
...
std::shared_ptr<PriorityNode> left, right; // Default constructs to nullptr
...
friend PriorityNode combine(const std::shared_ptr<PriorityNode> lhs,
const std::shared_ptr<PriorityNode> rhs) {
PriorityNode ret = PriorityNode("", ret_freq);
ret.setLeft(lhs);
ret.setRight(rhs);
return ret;
};
The empty string is "". You can pass that to the existing constructor. std::string doesn't distinguish empty strings and NULL pointers like C strings. It is not allowed to initialize an std::string with a NULL pointer. If you need a separate null state, use std::optional<std::string>.
Brief
I am defining my own String class. Everything works fine except for my definition of += which I intend to use to concatenate Strings
//expected behaviour
String c = "foo";
String d = "lala";
c+=d;
cout<<c;
Should output:
foolala
I am having an issue with it as it does seem to work correctly except for the last bit where it seems to not be passing the pointer.
Here is the code (I have omitted most of the other definitions as I don't think they are useful for this)
Code
class String{
private:
unsigned int SizeS;
char *Buffer;
public:
String():SizeS(0){}
String(unsigned int i):SizeS(i){Buffer=new char[SizeS];}
String(const char *string)
{
//defines the initialiser
SizeS = strlen(string); //find out the length of the string
Buffer = new char[SizeS]; //allocate space for the entire string+1 for terminator
memcpy(Buffer,string,SizeS); //copy to buffer the whole thing
Buffer[SizeS]=0; //terminate the buffer with an end character
}
char * GetBuffer() const { return this->Buffer; }
String (const String& copied) :SizeS(copied.SizeS)
{
// defines how copying works
Buffer = new char[SizeS];
memcpy(Buffer,copied.Buffer,SizeS);
}
// this is where the issue is ------------------
String* operator += (const String& to_concat)
{
unsigned int newSize = this->SizeS + to_concat.SizeS;
String *p = new String(newSize) ;
memcpy(p->Buffer,this->Buffer,this->SizeS);
memcpy(p->Buffer+this->SizeS,to_concat.Buffer,to_concat.SizeS);
std::cout<<p->Buffer<<std::endl;
return p;
}
// this is where the issue ends ------------------
};
std::ostream& operator<< (std::ostream& stream, const String& other) { stream << other.GetBuffer(); return stream; }
int main()
{
String c="foo";
std::cout<<c<<std::endl;
c += c;
std::cout<<c<<std::endl;
}
Expected Output
foo
foofoo
foofoo
Actual Output
foo
foofoo
foo
Question
What am I doing wrong? From my understanding, I am overwritting the pointer c with the pointer p, but it seems as though c does not change. Why is that ?
Solution
After reading the comments and suggestion I came up with this solution which works.
String& operator += (const String& to_concat)
{
unsigned int newSize = this->SizeS + to_concat.SizeS;
char* p = new char[newSize];
memcpy(p,this->Buffer,this->SizeS);
memcpy(p+this->SizeS,to_concat.Buffer,to_concat.SizeS);
delete[](this->Buffer);
this->Buffer=p;
this->SizeS=newSize;
return *this;
}
Because you didn't write any code to change c.
There is no "pointer c" and, even if there were, you'd not be overwriting it.
Your += creates a new, dynamically allocated string, using the data from the original two strings, then returns a pointer to it which your program then throws away (incidentally, leaking that new string).
Instead of creating and returning p, you should be modifying the buffer in this (then, conventionally, returning *this as a String& to permit chaining).
Also, a += operator should not produce output.
A + operator could work similarly to how you've done it, since they're supposed to produce new objects, but you shouldn't actually use new for that — you'd still have a memory leak. Try to avoid dynamic allocation (though you're going to need to dynamically allocate the buffer that each String's buffer pointer point to).
It might not be advisable according to what I have read at a couple of places (and that's probably the reason std::string doesn't do it already), but in a controlled environment and with careful usage, I think it might be ok to write a string class which can be implicitly converted to a proper writable char buffer when needed by third party library methods (which take only char* as an argument), and still behave like a modern string having methods like Find(), Split(), SubString() etc. While I can try to implement the usual other string manipulation methods later, I first wanted to ask about the efficient and safe way to do this main task. Currently, we have to allocate a char array of roughly the maximum size of the char* output that is expected from the third party method, pass it there, then convert the return char* to a std::string to be able to use the convenient methods it allows, then again pass its (const char*) result to another method using string.c_str(). This is both lengthy and makes the code look a little messy.
Here is my very initial implementation so far:
MyString.h
#pragma once
#include<string>
using namespace std;
class MyString
{
private:
bool mBufferInitialized;
size_t mAllocSize;
string mString;
char *mBuffer;
public:
MyString(size_t size);
MyString(const char* cstr);
MyString();
~MyString();
operator char*() { return GetBuffer(); }
operator const char*() { return GetAsConstChar(); }
const char* GetAsConstChar() { InvalidateBuffer(); return mString.c_str(); }
private:
char* GetBuffer();
void InvalidateBuffer();
};
MyString.cpp
#include "MyString.h"
MyString::MyString(size_t size)
:mAllocSize(size)
,mBufferInitialized(false)
,mBuffer(nullptr)
{
mString.reserve(size);
}
MyString::MyString(const char * cstr)
:MyString()
{
mString.assign(cstr);
}
MyString::MyString()
:MyString((size_t)1024)
{
}
MyString::~MyString()
{
if (mBufferInitialized)
delete[] mBuffer;
}
char * MyString::GetBuffer()
{
if (!mBufferInitialized)
{
mBuffer = new char[mAllocSize]{ '\0' };
mBufferInitialized = true;
}
if (mString.length() > 0)
memcpy(mBuffer, mString.c_str(), mString.length());
return mBuffer;
}
void MyString::InvalidateBuffer()
{
if (mBufferInitialized && mBuffer && strlen(mBuffer) > 0)
{
mString.assign(mBuffer);
mBuffer[0] = '\0';
}
}
Sample usage (main.cpp)
#include "MyString.h"
#include <iostream>
void testSetChars(char * name)
{
if (!name)
return;
//This length is not known to us, but the maximum
//return length is known for each function.
char str[] = "random random name";
strcpy_s(name, strlen(str) + 1, str);
}
int main(int, char*)
{
MyString cs("test initializer");
cout << cs.GetAsConstChar() << '\n';
testSetChars(cs);
cout << cs.GetAsConstChar() << '\n';
getchar();
return 0;
}
Now, I plan to call the InvalidateBuffer() in almost all the methods before doing anything else. Now some of my questions are :
Is there a better way to do it in terms of memory/performance and/or safety, especially in C++ 11 (apart from the usual move constructor/assignment operators which I plan to add to it soon)?
I had initially implemented the 'buffer' using a std::vector of chars, which was easier to implement and more C++ like, but was concerned about performance. So the GetBuffer() method would just return the beginning pointer of the resized vector of . Do you think there are any major pros/cons of using a vector instead of char* here?
I plan to add wide char support to it later. Do you think a union of two structs : {char,string} and {wchar_t, wstring} would be the way to go for that purpose (it will be only one of these two at a time)?
Is it too much overkill rather than just doing the usual way of passing char array pointer, converting to a std::string and doing our work with it. The third party function calls expecting char* arguments are used heavily in the code and I plan to completely replace both char* and std::string with this new string if it works.
Thank you for your patience and help!
If I understood you correctly, you want this to work:
mystring foo;
c_function(foo);
// use the filled foo
with a c_function like ...
void c_function(char * dest) {
strcpy(dest, "FOOOOO");
}
Instead, I propose this (ideone example):
template<std::size_t max>
struct string_filler {
char data[max+1];
std::string & destination;
string_filler(std::string & d) : destination(d) {
data[0] = '\0'; // paranoia
}
~string_filler() {
destination = data;
}
operator char *() {
return data;
}
};
and using it like:
std::string foo;
c_function(string_filler<80>{foo});
This way you provide a "normal" buffer to the C function with a maximum that you specify (which you should know either way ... otherwise calling the function would be unsafe). On destruction of the temporary (which, according to the standard, must happen after that expression with the function call) the string is copied (using std::string assignment operator) into a buffer managed by the std::string.
Addressing your questions:
Do you think there are any major pros/cons of using a vector instead of char* here?
Yes: Using a vector frees your from manual memory management. This is a huge pro.
I plan to add wide char support to it later. Do you think a union of two structs : {char,string} and {wchar_t, wstring} would be the way to go for that purpose (it will be only one of these two at a time)?
A union is a bad idea. How do you know which member is currently active? You need a flag outside of the union. Do you really want every string to carry that around? Instead look what the standard library is doing: It's using templates to provide this abstraction.
Is it too much overkill [..]
Writing a string class? Yes, way too much.
What you want to do already exists. For example with this plain old C function:
/**
* Write n characters into buffer.
* n cann't be more than size
* Return number of written characters
*/
ssize_t fillString(char * buffer, ssize_t size);
Since C++11:
std::string str;
// Resize string to be sure to have memory
str.resize(80);
auto newSize = fillSrting(&str[0], str.size());
str.resize(newSize);
or without first resizing:
std::string str;
if (!str.empty()) // To avoid UB
{
auto newSize = fillSrting(&str[0], str.size());
str.resize(newSize);
}
But before C++11, std::string isn't guaranteed to be stored in a single chunk of contiguous memory. So you have to pass through a std::vector<char> before;
std::vector<char> v;
// Resize string to be sure to have memor
v.resize(80);
ssize_t newSize = fillSrting(&v[0], v.size());
std::string str(v.begin(), v.begin() + newSize);
You can use it easily with something like Daniel's proposition
Hi I have a test tomarrow and can't figure out why subtraction is made on the pointer before checking if the refcount is 0. I've been searching on google but still cant figure it out. So I'm hoping turning to you guys :) would help.
Easyiest is too just show you the code, I've marked the lines with comments, so here it is:
This is the class StringRep that has pointers to it for counting pointerref to it,
struct StringRep{
int size; // amount of chars incl. EOL \0-tecken
char* chars; // Pointer to char
int refCount; // Amount of String-variables
};
And this is class String that uses the StringRep,
class String{
public:
String(char* str);
String(const String& other);
~String();
const String& operator=(const String& rhs);
char get(int index) const { return srep->chars[index]; }
void put(char ch, int index);
private:
StringRep* srep;
};
String::String(const String& other):srep(other.srep){
srep->refCount++;
}
String::~String(){
if (--srep->refCount == 0){ //why --srep here?
delete [] srep->chars;
delete srep;
}
}
const String& String::operator=(const String& rhs){
if (srep != rhs.srep){
if (--srep->refCount == 0){ //why --srep here?
delete [] srep->chars;
delete srep;
}
srep = rhs.srep;
srep->refCount++;
}
return *this;
}
void String::put(char ch, int index){
if (srep->refCount > 1){ //Why not --srep here?
StringRep* tmpRep = new StringRep;
tmpRep->refCount = 1;
tmpRep->size = srep->size;
tmpRep->chars = new char[tmpRep->size];
std::strcpy(tmpRep->chars, srep->chars);
--srep->refCount;
srep = tmpRep;
}
srep->chars[index] = ch;
}
This is all info I have on the example question for the test, I know that --spek points to the object before spek, but cant figure out the logic behing checking if what is pointed at before now is 0 then its okey to delete, or to copy, but why? As I said I've searched the webb and have found some answers to help me understand the functions of the pointer and the subtraction etc, it more the logic that is confusing.
Best regards
Because of operator precendence, --srep->refCount is not decrementing srep, but the refCount member.
So, the code is decrementing the refCount, and if it comes down to 0, it can assume that the last reference to the object is being destroyed.
--srep->refCount
is parsed as
--(srep->refCount)
because prefix decrement has lower priority than -> (however, postfix decrement has the same priority as ->). Always use parens in your own code!
I am modifying a function that accepts a const char* and uses a function, ProcessString. ProcessString is a function that expects a null-terminated character buffer as a char*. The characters in the buffer may or may not be modified, as defined by the function signature below. To "bridge the gap", I am using a temporary std::string:
void ProcessString( char* str );
void SomeFunction( const char* str )
{
string temp(str);
ProcessString( &temp[0] );
}
My primary question is about the guarantees of std::string::operator[] and whether the address returned by the &temp[0] above is a usable, null-terminated buffer as a char*. Secondly, and very much secondly, is there a better way to do this?
I am using C++03.
That only has well-defined behavior in C++11; in previous standards, std::string did not guarantee contiguous storage for its internal buffer.
However while that code is completely fine in C++11, the more idiomatic approach is to use std:vector<char>, which has guaranteed contiguous storage since C++03:
void ProcessString(char* str);
void SomeFunction(char const* str)
{
// + 1 for terminating NUL
std::vector<char> temp(str, str + std::strlen(str) + 1);
ProcessString(&temp[0]); // or temp.data() in C++11
}
I have created a small class to face this kind of problem, I have implemented the RAII idiom.
class DeepString
{
DeepString(const DeepString& other);
DeepString& operator=(const DeepString& other);
char* internal_;
public:
explicit DeepString( const string& toCopy):
internal_(new char[toCopy.size()+1])
{
strcpy(internal_,toCopy.c_str());
}
~DeepString() { delete[] internal_; }
char* str() const { return internal_; }
const char* c_str() const { return internal_; }
};
And you can use it as:
void aFunctionAPI(char* input);
// other stuff
aFunctionAPI("Foo"); //this call is not safe. if the function modified the
//literal string the program will crash
std::string myFoo("Foo");
aFunctionAPI(myFoo.c_str()); //this is not compiling
aFunctionAPI(const_cast<char*>(myFoo.c_str())); //this is not safe std::string
//implement reference counting and
//it may change the value of other
//strings as well.
DeepString myDeepFoo(myFoo);
aFunctionAPI(myFoo.str()); //this is fine
I have called the class DeepString because it is creating a deep and unique copy (the DeepString is not copyable) of an existing string.
If you need to go from a const char* to a char *, why not use strdup, and then free the buffer when ProcessString is done?
Going through std::string seems unnecessary to me.