Archived

This topic is now archived and is closed to further replies.

std::string wrapper class?

This topic is 5113 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, For the longest time I thought this was possible, but never tried to do it. Now that I did, I ran into some problems. Up until now, my String class was nothing more than
typedef std::string String; 
- but it''s not enough anymore. I wanted to make a small wrapper class, and add some of my own functions that I''d need. How would I do it? Sure, I could make a whole class with a private std::string member, and provide functionality to it by copying all the calls. So for each function/constructor that std::string has, I''d need an identical copy that calls the function in std::string. But that''s just plain stupid - rewriting what''s already done, causing an additional and unnecessary overhead. Simply a no-no. So I thought something like this would do the trick...
class String : public std::string {}; 
Apparently not. All I wanna do is copy all the functions/constructors that std::string has, and add a couple of my own. Isn''t this possible? ---------------------------------------------------------------- On another topic... I know I could convert a std::string into all uppercase or all lowercase by using std::transform and std::toupper/std::tolower. But is there a way to compare two strings, ignoring the case? An equivalent to Java''s String.equalsIgnoreCase(String) function. I was thinking something along std::equals(String1.begin(), String1.end(), String2.begin(), ???); But what goes in the question marks? I don''t want to write a function of my own, as I''m sure std covers that somewhere... But where? Thanks in advance, shurcooL` ---
shurcooL`

Share this post


Link to post
Share on other sites
"Inheriting from std::string is not useful, you cannot override any of
its functions (because none of them are virtual,) and you cannot use
std::string's protected interface (because it doesn't have one.) The
only thing you could do by inheriting std::string is to add functions to
it, which can be done just as easily with global functions that accept a
std::string pointer/reference"



Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

[edited by - rohde on December 13, 2003 10:26:25 PM]

Share this post


Link to post
Share on other sites
I do not know of a single common function for a case-insensitive compare, but I do know that the standard C++ libraries for the GNU C++ Compiler include a function called strcasecmp(char *, char *) and strncasecmp(char *, char *, size_t).

If it works, wonderful. If it doesn''t, then hopefully someone else answers the question properly either while I''m typing this or before you check back.



Remember: Silly is a state of Mind, Stupid is a way of Life.
-- Dave Butler

Share this post


Link to post
Share on other sites
On the string compare issue:

No there's is no such function in the Standard. I dug this up:

bool CharsEqualIgnoreCase(char x, char y)
{
static locale loc;
return toupper(x, loc) == toupper(y, loc);
}

bool StringsEqualIgnoreCase(const string& S1, const string& S2)
{
if (S1.length() == S2.length())
{
return equal(S1.begin(), S1.end(), S2.begin, CharsEqualIgnoreCase);
}
else
return false;
}


It's from one of my old files, but I don't know who wrote it originally actually, but it's good.
You should of course call the StringsEqualIgnoreCase from your code like this: StringsEqualIgnoreCase(string1, string2);.



Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

[edited by - rohde on December 13, 2003 10:45:37 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by rohde
"Inheriting from std::string is not useful, you cannot override any of
its functions (because none of them are virtual,) and you cannot use
std::string's protected interface (because it doesn't have one.) The
only thing you could do by inheriting std::string is to add functions to
it
, which can be done just as easily with global functions that accept a
std::string pointer/reference"


So you're saying it's possible, but not recommended?

Beer Hunter and Indigo Darkwolf: Thanks for the suggestions.

Looks like I'll end up using one of them. Still, I'd rather do it without converting them to c_str(), but whatever works.

Edit: rohde, that's exactly what I was talking about. Only thing is, I was hoping that the CharsEqualIgnoreCase function would be built into std. :| Oh, well.

BTW, what's the purpose of 'static locale loc;'? Because toupper only needs one parameter.

---
shurcooL`

[edited by - shurcooL on December 13, 2003 10:45:57 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by shurcool
quote:
Original post by rohde
"Inheriting from std::string is not useful, you cannot override any of
its functions (because none of them are virtual,) and you cannot use
std::string's protected interface (because it doesn't have one.) The
only thing you could do by inheriting std::string is to add functions to
it
, which can be done just as easily with global functions that accept a
std::string pointer/reference"


So you're saying it's possible, but not recommended?

Beer Hunter and Indigo Darkwolf: Thanks for the suggestions.

Looks like I'll end up using one of them. Still, I'd rather do it without converting them to c_str(), but whatever works.

And so there's nothing on std::equals? It looked just like what I needed.

---
shurcooL`

[edited by - shurcooL on December 13, 2003 10:40:54 PM]



Yeah it is possible. Sure. But it won't give you anything extra, at all! If you need some extra functions to work on strings you might as well define them outside a class and put them in a namespace for packaging. That would be a very common C++ thing to do actually.




Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

[edited by - rohde on December 13, 2003 10:46:28 PM]

Share this post


Link to post
Share on other sites
On toupper and locale:

The toupper from my code is the toupper from The C++ Standard Library, NOT the toupper from CRT (C Run-Time).

The C++ toupper takes two arguments:


template
CharType toupper(
CharType _Ch,
const locale& _Loc
) const;

Parameters
_Ch
The character to be converted to upper case.
_Loc
The locale containing the character to be converted.




Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

[edited by - rohde on December 13, 2003 10:52:24 PM]

Share this post


Link to post
Share on other sites
I don''t know why, but...

String test = "AbCdE zzz-ZZZAP!";
if (test.substr(test.begin() + test.find(''z''), test.end() - test.begin()).toupper()
== "ZZZ-ZZZAP!") return true;
Makes more sense to me than...

String test = "AbCdE zzz-ZZZAP!";
if (toupper(test.substr(test.begin() + test.find(''z''), test.end() - test.begin()))
== "ZZZ-ZZZAP!") return true;
Also, take a look at the edit in my previous post.

---
shurcooL`

Share this post


Link to post
Share on other sites

template<class _Ty>
struct equal_to_insensitive : binary_function<_Ty, _Ty, bool>
{
bool operator()(const _Ty& _X, const _Ty& _Y) const
{
return (std::tolower(_X) == std::tolower(_Y));
}
};

std::equals(String1.begin(), String1.end(), String2.begin(), equal_to_insensitive());

Share this post


Link to post
Share on other sites
I want to start by saying I think all the posts so far are quite good ... so don''t take this as negative critisism, I just want to add some insight / alternate ideas

1. IT IS USEFULL to inheirit from classes EVEN if you don''t get virtual functions ... because virtual / non-virtual functions are only needed if you want polymorphic behavior, but if you are in control of the client, you CAN overide non-virtual functions ... here''s an example:

some_client_func(string str1);
// this function will only be able to access the functions in string, and if it calls functions you''ve overidden it won''t get those versions ...

your_client(String str1);
// calls inside this function to members your overidden WILL call YOUR version ... virtual is not only not needed for this, but would add a run-time performance cost ...

VIRTUAL is ABSOLUTELY only needed if you want the client to get different behavior based on the client''s run-time class WITHOUT KNOWING which class he has ...

2. I was gonna say something about it C++ avoiding some of the old case-insensitive stuff, because it''s now harder with locales and different size strings (you know anything in the standard has to work on wide strings, unicode strings, and ascii strings ... so it''s a lot of work for each extra feature) ...

BUT I would have been wrong, because since they already have to_upper or to_lower, that knowledge is already in there, and wrapping it more convieniently shouldn''t have been too hard ...

and I just reread some of your last posts while typing, I think you''ve got it just fine ...

Share this post


Link to post
Share on other sites
quote:
Original post by Xai
I want to start by saying I think all the posts so far are quite good ... so don't take this as negative critisism, I just want to add some insight / alternate ideas

1. IT IS USEFULL to inheirit from classes EVEN if you don't get virtual functions ... because virtual / non-virtual functions are only needed if you want polymorphic behavior, but if you are in control of the client, you CAN overide non-virtual functions ... here's an example:



Everything that can be placed in a derived class of std::string could also be made a global function usable to everyone who has a std::string object, so why limit the use of the functions to only objects of the specific derived class?




Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

[edited by - rohde on December 13, 2003 11:20:11 PM]

[edited by - rohde on December 13, 2003 11:20:39 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by rohde
Yeah it is possible. Sure.
So how exactly would I do this? Because I'd rather have my additional string functions be inside the class, rather than outside as global functions (as demonstrated in the code snippet above).

I tried this, and it works just fine, as I get all the std::string functions, only problem is when I try to assign a string to it.

class String : public std::basic_string
{
};
I get 'error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const char [5]' (or there is no acceptable conversion)'. Same happens when I try to initialize it with a string (String test = "blah1"; ). Why is that, and how do I fix it?

Edit: Seems that just the assignment operator doesn't work... Because test.assign("blah1); works flawlessly.

---
shurcooL`

[edited by - shurcooL on December 13, 2003 11:35:37 PM]

Share this post


Link to post
Share on other sites
First, let me reiterate that I strongly discourage what you're trying to do - it's bad design. For example, the string destructor is non-virtual which means that this spells troubles:

string *pString = new mystring("abc");
delete pString;

So be careful when using your class, you should be ok if you don't add any member fields (but if you do you could be in trouble since your destructor won't be called)

Further, I would argue that it is bad OOP. If you aren't modifying the class' behaviour, but only adding behaviour, then inheritence is the wrong mechanism. But alas, if you want to do it But be careful...good luck




Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

[edited by - rohde on December 13, 2003 11:41:58 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by shurcool
quote:
Original post by rohde
Yeah it is possible. Sure.
So how exactly would I do this? Because I''d rather have my additional string functions be inside the class, rather than outside as global functions (as demonstrated in the code snippet above).

I tried this, and it works just fine, as I get all the std::string functions, only problem is when I try to assign a string to it.

class String : public std::basic_string
{
};
I get ''error C2679: binary ''='' : no operator found which takes a right-hand operand of type ''const char [5]'' (or there is no acceptable conversion)''. Same happens when I try to initialize it with a string (String test = "blah1"; ). Why is that, and how do I fix it?

Edit: Seems that just the assignment operator doesn''t work... Because test.assign("blah1); works flawlessly.

---
shurcooL`

[edited by - shurcooL on December 13, 2003 11:35:37 PM]


Because std::string has all the operators overloaded.. but since you''re class only inherits it and is NOT the class itself, any operators done (=, +, etc) would have to be created manually, so:

class String : public std::basic_string
{
operator =(const char *str)
{
this->assign(str); //Call the assignment function manually
}
};


Or, you could cast as an std::string and do a direct assignment (which would call the std::string assignment operator).

Share this post


Link to post
Share on other sites
Oh, I didn''t realize that. That''s why the assignment operator didn''t work above.

I also just came up with the exact same thing as Ready4Dis, and that''s not pretty. I was hoping to just clone this class, and then add a few functions of my own, but I guess that can''t be done. Sad. In UnrealScript, this could be done quite easily. ;\ Oh well, I guess I''ll have a global function then. Thanks to all for help.

---
shurcooL`

Share this post


Link to post
Share on other sites
Ok then.

Firstly rohde...get a new OO design text, seriously (no offence). If one modifies the base objects behaviour then one is effectively creating a new behaviour and thus is creating what should be a new ''class'' or object. This contradicts the ''is-a'' relationship where the interface of the base class should not be fucked around with (not so much when one isn''t using polymorphism however). For an example std::iostream ''extends'' the functionality of std::ios_base (among other classes).

Ready4Dis pointed out exactly what you need to do to get assignments working. I would advise you also check the msdn docs on operator overloading (or your friendly text) and make sure you know it well. I would give a few optimization (as in code structure not performance) tips but I''ve not coded in C++ for a while and my computer''s in the shop (darn).

< krysole || krysollix >
Sleep, caffeine for the weak minded!

Share this post


Link to post
Share on other sites
Another option is just taking my string class that I wrote and extending it to do what you need .

I have the PreAllocateSize set at 64, this means each string is at minimum 64-bytes, but the upside is, it only needs to allocate memory every 64-bytes . I meant to make this able to be set dynamically, but I never got around to it. It''s relatively easy to use and modify as well (although, not especially optimized at the moment).


#ifndef MyString_H
#define MyString_H
#include <Memory.h>

#define PreAllocateSize 64

/*** A simple string class ***/
class String_C
{
public:
char *Str;
long StrLen, StrMax, Hash, DataLen;
public:
String_C(void) { Str=0; StrMax=0; DataLen = 0; Unload(); };
void Unload(void)
{
if (Str)
delete [] Str;
Hash = 0;
Str = 0;
DataLen = 0;
StrLen=0;
};
~String_C(void) { Unload(); };

public:
void CreateHash(void)
{
long ctr;
Hash=0xffffffff;
for (ctr=0;ctr!=StrLen;++ctr)
Hash+=~Str[ctr];
Hash^=0xffffffff;
}

void operator =(String_C &src)
{
// if (DataLen<src.StrLen+1)

// {

Unload(); //Unload everything now :)

DataLen = src.StrLen + PreAllocateSize; //Allocate a few extra bytes :)

Str = new char[DataLen];
// } //Otherwise, we can store it without re-allocating!

StrLen = src.StrLen;
memcpy(Str,src,StrLen);
Str[StrLen]=0;
};

void operator =(char *src)
{
long ctr=0;
Unload(); //Unload us first!

if (src==0 || src[0]==0)
return; //Just unload the string!

while (src[ctr])
{
++ctr;
}; //While we have something!

StrLen = ctr;
DataLen = StrLen + PreAllocateSize;
Str = new char[DataLen];
memcpy(Str,src,StrLen);
Str[StrLen]=0;
};

operator char*()
{
return Str;
};
bool operator ==(char *cmp)
{
char *tmp = Str;
while (*cmp && *tmp)
{
if (*cmp != *tmp)
return false; //An unequal value1

++cmp;
++tmp;
}

if (*cmp!=*tmp)
return false; //Only one null terminated

return true;
}
bool operator ==(String_C &cmp)
{
int ctr;
if (cmp.StrLen!=StrLen) //different lengths can''t be equal!

return false;
if (Hash && cmp.Hash)
if (Hash!=cmp.Hash)
return false; //Quick out!

for (ctr=0;ctr!=StrLen;++ctr) //Check to see if we''re the same!

if (cmp.Str[ctr] != Str[ctr]) //Different characters?

return false;
//Everything checks out!

return true;
};
bool operator !=(String_C &cmp)
{
return !(*this==cmp);
};
void operator --(void)
{
if (StrLen==0) return; //Can''t go back any further!

--StrLen;
Str[StrLen] = 0; //Set a null at the end

}

void operator +=(char c)
{
if (StrMax && StrLen==StrMax) return; //Do nothing if we hit our max size!

++StrLen;
if (!Str)
{
DataLen = PreAllocateSize;
Str = new char[DataLen];
Str[0]=0;
}
if (StrLen >= DataLen-1) //More than we have?

{
char *tmp;
DataLen = StrLen + PreAllocateSize;
tmp = new char[DataLen];
memcpy(tmp,Str,StrLen);
delete [] Str;
tmp[StrLen-1]=c;
tmp[StrLen]=0;
Str = tmp;
}
else
{
Str[StrLen-1] = c;
Str[StrLen] = 0;
}
}

void operator +=(char *str)
{
while (*str)
*this+=*str++;
}

void operator +=(String_C src)
{
/* char *tmp;
tmp = new char[StrLen + src.StrLen + 1];
memcpy(tmp,Str,StrLen); //copy in first string
memcpy(tmp+StrLen,src.Str,src.StrLen); //Copy in second string
*(tmp+StrLen+src.StrLen) = 0;
delete[] Str;
Str = tmp;
StrLen+=src.StrLen;
*/

int ctr;
for (ctr=0;ctr!=src.StrLen;++ctr)
*this += src.Str[ctr];
}
};

#endif


To use it, you would simply:

String_C Test, Test2;
Test = "This is a test"; //Assign a string

Test2 = Test; //Makes a copy of Test..

Test+= ''!''; //Add a character

--Test; //Take off the last character!

Test.CreateHash(); //Creates a hash value of the current string for quick compares!

Test2.CreateHash();

if (Test == Test2) //Compare 2 strings

/* If both have a hash value, it compares that first, then the
length, then the string itself, so it has a few quick-outs.
*/



Also, I overloaded the cast to a char*, so if a function requires a character array, you can simply do this:

void SomeFunc(char *test)
{
cout << test;
}

String_C Test;
Test = "Testing";
SomeFunc(Test); //Automagically returns the null-terminated char array.

Share this post


Link to post
Share on other sites
quote:
Original post by Krysole
Ok then.

Firstly rohde...get a new OO design text, seriously (no offence). If one modifies the base objects behaviour then one is effectively creating a new behaviour and thus is creating what should be a new ''class'' or object. This contradicts the ''is-a'' relationship where the interface of the base class should not be fucked around with (not so much when one isn''t using polymorphism however). For an example std::iostream ''extends'' the functionality of std::ios_base (among other classes).


Do you know C++? This paragraph does not make much sense.

The issue here is inheriting from std::string, if you''re seriously advocating this you are not much of a C++ programmer, sorry.




Human beings, by changing the inner attitudes of their minds, can change the outer aspects of their lives.

William James (1842 - 1910)

Share this post


Link to post
Share on other sites
I found this debate on the subject of private inheritance vs. containment, especially when it comes to inheriting from std::string... I thought I''d post it here, as some of you may be interested.

Here.

I would do containment, like many of them recommended, but like I said earlier, it''s too much work having to copy all the constructors/member functions of std::string interface into mine, and I simply object to this kind of approach. It''s not right. Why should I rewrite the entire interface of a class, just to add a few of my own functions?

So I tried ironpoint''s code, and it''s far from working. First of all, what in the world is binary_function? I''ve searched through all my VC.net includes, and there isn''t such thing. Where did you get it from?

Please help me get it to work as a global function for now... Thanks.

PS. Ready4Dis, thanks a lot for that code, but I''d rather stick with the standard string for now, as this is just a very minor aspect of my project (it''s for a config file parser; to get a boolean statement, I need to compare a string parameter in the config file to "Yes", ignoring case).

---
shurcooL`

Share this post


Link to post
Share on other sites
Never mind now, I've figured it out.

If anyone ever needs something like this, I'll post it for your reference.

This goes in a CPP file.

template <class T>
struct equal_to_ignore_case : public std::binary_function<T, T, bool> {
result_type operator()(const T& first_argument_type, const T& second_argument_type) const
{
return std::toupper(first_argument_type) == std::toupper(second_argument_type);
}
};

bool StringIgnoreCaseCompare(String sString1, String sString2)
{
if (sString1.length() == sString2.length())
return std::equal(sString1.begin(), sString1.end(), sString2.begin(), equal_to_ignore_case<std::string::value_type>());
else return false;
}
Keep in mind this uses a basic <cctype> toupper function, so if you need support for wide characters and what not, use toupper(charT, locale) from <locale>.

---
shurcooL`

[edited by - shurcooL on December 14, 2003 4:51:55 PM]

Share this post


Link to post
Share on other sites
Am I completely missing something? Why didn''t you just do:

if( string1.toUpper() == string2.toUpper() );

For your case insensitive match? Why bother with templates and extra global funcs and all that fun stuff?

Share this post


Link to post
Share on other sites
_buu_, std::string doesn't have a toupper() method. You'd have to do it manually, like so.

#include <algorithm>
#include <cctype>
std::transform(str1.begin(), str1.end(), str1.begin(), std::toupper);
What I'm doing is the exact same thing, only I'm comparing it char by char. So if the first char is different in the two strings, it'll break out right away. Real handy if the strings are very long.

---
shurcooL`

[edited by - shurcooL on December 14, 2003 9:44:32 PM]

Share this post


Link to post
Share on other sites