Thread: Problems with my custom string class

  1. #1
    Registered User
    Join Date
    Oct 2005
    Posts
    271

    Problems with my custom string class

    Hello, everyone.

    I've been trying to develop my own string class to understand the c++ language.

    In plain c++ environments, it works fine. But it has problems when I use it in an MFC environment.

    Here's the class and member function definition.

    Code:
    class CStr
    {
    	char* pData;
    	int nLength;
    	int stlen(char* c){for(int i = 0; *c != '\0'; c++, i++); return i;}
    	int stlen(const char* c){for(int i = 0; *c != '\0'; c++, i++); return i;}
    	char* stcpy(char*, const char*);
    	char* stcat(char**, const char*);
    public:
    	CStr();
    	CStr(char*);
    	CStr(CStr&);
    	~CStr(){delete [] pData;}
    	void set(char* c){delete pData; pData = new char[stlen(c) + 1]; stcpy(pData, c);}
    	char* get(){return pData;}
    	int getlength(){return nLength;}
    	void cat(char* c){stcat(&pData, c);}
    	CStr& operator=(const char* c){set((char*) c); return *this;}
    	CStr& operator=(CStr &c){set(c.get());return *this;}
    	CStr& operator+=(const char* c){stcat(&pData, c); return *this;}
    	CStr& operator+=(CStr &c){stcat(&pData, c.get()); return *this;}
    	CStr friend operator+(CStr&, CStr&);
    	CStr friend operator+(CStr&, char*);
    	CStr friend operator+(char*, CStr&);
    };
    
    char* CStr::stcat(char** dest, const char* src)
    {
    	char* p;
    	char* q;
    	char* r;
    	r = p = *dest;
    	q = *dest = new char[nLength + stlen(src) + 1];
    	while(*(q++) = *(p++));
    	q--;
    	while(*(q++) = *(src++));
    	nLength = stlen(*dest);
    	delete [] r;
    	return *dest;
    }
    
    char* CStr::stcpy(char* dest, const char* src)
    {
    	char* p;
    	p = dest;
    	while(*(p++)=*(src++));
    	nLength = stlen(dest);
    	return dest;
    }
    
    CStr::CStr()
    {
    	pData = new char[1];
    	*pData = '\0';
    	nLength = 0;
    }
    
    CStr::CStr(char* c)
    {
    	pData = new char[stlen(c)+1];
    	stcpy(pData, c);
    	nLength = stlen(pData);
    }
    
    CStr::CStr(CStr &c)
    {
    	pData = new char[c.getlength()+1];
    	stcpy(pData, c.get());
    	nLength = stlen(pData);
    }
    
    CStr operator+(CStr &dest, CStr &src)
    {
    	CStr new_string(dest);
        new_string.cat(src.get());
    	return new_string;
    }
    
    CStr operator+(CStr &dest, char* src)
    {
    	CStr new_string(dest);
        new_string.cat(src);
    	return new_string;
    }
    
    CStr operator+(char* dest, CStr &src)
    {
    	CStr new_string(dest);
        new_string.cat(src.get());
    	return new_string;
    }
    I think there's nothing wrong with the code.

    But when I use my own string with the function wsprintf or TextOut (as a member of CPaintDC), it creates some kind of protection fault.

    Could anybody tell me why this is, and how I could improve it?

    If it's too vague, I can also post the Windows code.

    Thanks!

  2. #2
    Registered User
    Join Date
    Jan 2005
    Posts
    847
    Quote Originally Posted by cunnus88
    But when I use my own string with the function wsprintf or TextOut (as a member of CPaintDC), it creates some kind of protection fault.

    Could anybody tell me why this is, and how I could improve it?

    If it's too vague, I can also post the Windows code.

    Thanks!
    Implament a function to return a pointer to the string data and pass that to functions that expect a char*

  3. #3
    Registered User
    Join Date
    Apr 2003
    Posts
    2,663
    One other note: string literals are const, so you should get a compiler error when you assign a string literal to a non-const variable: char* pData. Normally, a non-const variable like pData would be able to change the value it points to. However, if you try it, you will get a runtime error(v. a compiler error). The reason the compiler doesn't catch that as an error is because of backward compatibility reasons when string literals were not constant.

    So, you should always declare char* variables that point to string literals as const: const char* pData. That way if you mistakenly try to change the value the pointer points to, you will get a compiler error instead of a runtime error. You always want to try to write code so that if you make an error the compiler catches it rather then getting a nasty runtime error.

  4. #4
    Registered User
    Join Date
    Apr 2003
    Posts
    2,663
    You have to give functions the arguments they expect:
    Syntax
    int wsprintf( LPTSTR lpOut, LPCTSTR lpFmt, …argument );

    Parameters
    lpOut
    [out] Long pointer to a buffer to receive the formatted output. The maximum size of the buffer is 1024 bytes.
    lpFmt
    [in] Long pointer to a null-terminated string that contains the format-control specifications. In addition to ordinary ASCII characters, a format specification for each argument appears in this string. For more information about the format specification, see the Remarks section.
    ...
    [in] Specifies one or more optional arguments. The number and type of argument parameters depend on the corresponding format-control specifications in the lpFmt parameter.
    virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount );

    BOOL TextOut( int x, int y, const CString& str );

    Return Value

    Nonzero if the function is successful; otherwise 0.

    Parameters

    x

    Specifies the logical x-coordinate of the starting point of the text.

    y

    Specifies the logical y-coordinate of the starting point of the text.

    lpszString

    Points to the character string to be drawn.

    nCount

    Specifies the number of bytes in the string.

    str

    A CString object that contains the characters to be drawn.
    LPSTR A 32-bit pointer to a character string.


    LPCTSTR A 32-bit pointer to a constant character string that is portable for Unicode and DBCS.


    LPTSTR A 32-bit pointer to a character string that is portable for Unicode and DBCS.

    CString objects also have the following characteristics:

    CString objects can grow as a result of concatenation operations.


    CString objects follow “value semantics.” Think of a CString object as an actual string, not as a pointer to a string.


    You can freely substitute CString objects for const char* and LPCTSTR function arguments.


    A conversion operator gives direct access to the string’s characters as a read-only array of characters (a C-style string).
    Tip Where possible, allocate CString objects on the frame rather than on the heap. This saves memory and simplifies parameter passing.
    Data Conversions:
    http://www.codeproject.com/cpp/data_conversions.asp

    char pointer to CString


    char * mystring = "12345";
    CString string = mystring;
    http://www.mvps.org/vcfaq/mfc/9.htm

    How can I convert a CString variable to char* or LPTSTR?

    This is a common question, due to the fact that several Win32 APIs take char* or LPTSTR parameters, and CString only provides a LPCTSTR overloaded operator, which won't work on these cases. You can go two ways:

    1. Use CString::GetBuffer(). It can be used in a manner similar to this:
    // prototype of a function that takes a LPTSTR parameter
    // presented for argument's sake.
    void test_func ( LPTSTR lpszString, int length );

    CString string;
    test_func ( string.GetBuffer ( 50 ), 50 );
    string.ReleaseBuffer ( );


    Forgetting to call CString::ReleaseBuffer() can cause problems very difficult to debug as it releases the lock on CString's inner buffer.

    One thing to keep in mind about CString::GetBuffer() is that it returns a TCHAR* value (or LPTSTR, it's the same), so it is subject to the same ANSI/MBCS Vs. UNICODE convertions as most other Win32 APIs. It also means that if you're compiling a unicode version of your application, and specifically need a char* from your CString instance, you'll have to use a separate buffer of the appropriate type, and then make the convertion to unicode using one of the available API's before asigning it's value to the CString instance. The same goes if you're doing the exact opposite: getting a WCHAR* out of a CString, while compiling in MBCS mode.
    2. Use a temporary variable. For example:


    char temp[256];
    CString string;

    test_func ( temp, 256 );
    string = temp;

    Last edited by 7stud; 11-15-2005 at 11:54 AM.

  5. #5
    Registered User
    Join Date
    Oct 2005
    Posts
    38
    you could also consider using the A2W() conversion function to avoid getting errors when assigning values to types like LPWSTR etc.

  6. #6
    Registered User
    Join Date
    Oct 2005
    Posts
    271
    Whoa, that's a lot to handle!

    Thanks for all the help guys.

    Just a few questions then to help out a noob if you don't mind (I would like to ask more but I don't understand your posts enough to ask the right ones).

    1. First Q
    string literals are const, so you should get a compiler error when you assign a string literal to a non-const variable: char* pData
    I'm not sure what a string literal is, but I assume it's something like the string type you find in Visual Basic, right? But if it's a constant, wouldn't I be unable to change the length of pData once CStr was instantiated? And that's what I didn't want to do. I wanted to try and create a string type like the one you find in VB.

    2. 2nd Q
    allocate CString objects on the frame rather than on the heap
    What's a "frame" and a "heap"? I looked it up and it seemed that they were some kind of virtual area in the memory used for different purposes (one for the stack and the other for dynamic memory allocation).

    Final note: I kind of figure the above questions are quite a bit to handle, so if you don't want to (or don't have the time to) I think it would be even better for me if you could just refer me to a book or web site that deals fairly comprehensively and straightforwardly the following:
    (1) the handling of "string literals" in c++
    (2) memory and memory allocation
    (3) the implementation of the above with MFC

    Again, thanks for all the feedback, people.

  7. #7
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    A string literal is a string in quotes: "Hello". I don't see any string literals in the code you posted, so I don't know if that applies. You are not using CStrings (an MFC string class) so that stuff doesn't really apply either.

    I'd suggest showing the code where you use wsprintf or TextOut, including the code that you use to initialize your CStr variable, and any code between initialization and the call to the "Windows" function.

  8. #8
    Registered User
    Join Date
    Apr 2003
    Posts
    2,663
    I'm not sure what a string literal is
    Anything between double quotes, e.g. "some text".

    But if it's a constant, wouldn't I be unable to change the length of pData once CStr was instantiated?
    In C++, when something is a const, you cannot change anything about it: not the length, not the first character, not the last character, etc.

    My point was that there is a type mismatch when you do this:

    char* pData = "some text";

    The left side is non-const, which says you can change whatever it points to, but the right side is a const by definition and therefore cannot be changed. The compiler does not catch that error. So, wen you are going to use a char* to point to a string, even if you don't understand why, do this:
    Code:
    const char* pData = "some text";
    edit: After reading Daved's comment, I see now that your constructors copy the chars from the string literals that are the function arguments into an array, so the resulting array is not const. Therefore, you don't need to declare pData as const char* pData.

    What's a "frame" and a "heap"? I looked it up and it seemed that they were some kind of virtual area in the memory used for different purposes (one for the stack and the other for dynamic memory allocation).
    In C++, you dynamically allocate memory with 'new', like you are doing here:
    Code:
    q = *dest = new char[nLength + stlen(src) + 1];
    For normal variables, the memory is released when a function ends, i.e. the local variables are destroyed when a function ends. On the other hand, memory allocated with 'new' is not released until you 'delete', so when a function ends it does not affect that memory. new and delete should always be used as a pair, which you take care of in your destructor. The memory allocated with 'new' is in an area called the "heap", and the memory allocated for normal variables is in a place called the "stack"(frame?).

    Final note: I kind of figure the above questions are quite a bit to handle, so if you don't want to (or don't have the time to) I think it would be even better for me if you could just refer me to a book or web site that deals fairly comprehensively and straightforwardly the following:
    (1) the handling of "string literals" in c++
    (2) memory and memory allocation
    (3) the implementation of the above with MFC

    Again, thanks for all the feedback, people.
    1) C++ takes any string literal, e.g. "some text", and slaps a '\0' character onto the end of it, places it in memory, and then returns its address. So, if you do this:

    const char* pData = "some text";

    C++ stores the value:

    some text\0

    in memory and returns its address, which is then assigned to pData. C++ also makes the string a const, so you can't change it. The string literal's type is const char[].

    2)Any beginning C++ book will explain the basics when discussing 'new', but it's not much more than what I already posted, and you don't need to know much more than that to use C++. However, there are experts on the forum that know all the details, so post a question about it if you need to know more of the specifics.

    3)MFC is something that usually comes long after learning C++. You need to have a good handle on C++ before venturing into that quagmire. For instance, it's fairly basic C++ to know that functions require the exact types that are in their definitions. You can't do this:
    Code:
    void someFunc(string str);
    ...
    ...
    int num = 10;
    someFunc(num)
    Often times, there are "implicit" type conversions that take place, e.g.:
    Code:
    void someFunc(double num)
    {
    	cout<<num<<endl;
    }
    ...
    ...
    
    int num = 10;
    someFunc(num);
    But that's because there are conversion functions defined somewhere, and the compiler assumes you want to call one of those when you use the wrong type in a function call. The conversion function then converts the function argument to the correct type, and the function call is completed without an error. However, you can't just assume there's a conversion function, which you found out with your code.
    Last edited by 7stud; 11-15-2005 at 02:51 PM.

  9. #9
    Registered User
    Join Date
    Apr 2003
    Posts
    2,663
    You are not using CStrings (an MFC string class) so that stuff doesn't really apply either.
    Whaaa?

    char* ---> CString which can then be used in functions requiring LPCTSTR types, e.g. wsprintf() and TextOut()

    char* ---> CString --->LPTSTR which can then be used in functions like wsprintf()

  10. #10
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    CString is an MFC class. LPTSTR, LPSTR, LPCTSTR, and LPCSTR are just typedefs used by Windows. MFC is not required for Windows programming.

    An LPTSTR in a non-Unicode environment is an LPSTR which is just a char*.
    An LPCTSTR in a non-Unicode environment is an LPCSTR which is just a const char*.

    There is no need for the CString when using functions that expect LPTSTR or LPCTSTR. You can just pass a char* or const char* directly to them. For example, you could also pass the result of c_str() on a C++ string to a function expecting an LPCTSTR or LPCSTR. The MFC CString class has an automatic conversion to LPCTSTR, so it will also work automatically with functions taking an LPCTSTR, but that doesn't mean it's required.

    In this case, using CString would defeat the entire purpose of learning by creating a string class from scratch.
    Last edited by Daved; 11-15-2005 at 03:19 PM.

  11. #11
    Registered User
    Join Date
    Oct 2001
    Posts
    2,934
    Code:
    >	void set(char* c){delete pData; pData = new char[stlen(c) + 1]; stcpy(pData, c);}
    This delete should be:
    Code:
    delete []pData;
    Code:
    >char* CStr::stcat(char** dest, const char* src)
    >{
    .
    .
    >	delete [] r;
    What if r (*dest) wasn't dynamically allocated with new, for example it's a plain char array?

    Also your assignment operator function should probably handle self assignment.
    Last edited by swoopy; 11-15-2005 at 04:26 PM.

  12. #12
    Registered User
    Join Date
    Oct 2005
    Posts
    271
    Not entirely out of the woods, but I'm seeing light.

    One question:

    Also your assignment operator function should probably handle self assignment.
    What is "self assignment"?

    And just for the curious, here's the code I'm tinkering with. I also managed to get rid of the protection error through a casting assignment (LPCTSTR).

    Code:
    #include <afxwin.h>
    #include <string>
    #include "my_wclasses.h"
    #include "cstr.h"
    
    using namespace std;
    
    CStr my_string;
    char BUF[256];
    int X = 1, Y = 1;
    
    CMainWin::CMainWin()
    {
    	Create(NULL, "Bare Bones String");
    }
    
    BOOL CApp::InitInstance()
    {
    	m_pMainWnd = new CMainWin;
    	m_pMainWnd->ShowWindow(SW_SHOW);
    	m_pMainWnd->UpdateWindow();
    	return TRUE;
    }
    
    afx_msg void CMainWin::OnPaint()
    {
    	CPaintDC dc(this);
    	dc.TextOut(X, Y, (LPCTSTR) my_string.get(), my_string.getlength());
    }
    
    afx_msg void CMainWin::OnRButtonDblClk(UINT nFlag, CPoint p)
    {
    	X = p.x;
    	Y = p.y;
    	wsprintf(BUF, "Right button is down at X: %d and Y: %d", X, Y);
    	my_string = BUF;
    	InvalidateRect(NULL, 0);
    }
    
    afx_msg void CMainWin::OnLButtonDblClk(UINT nFlag, CPoint p)
    {
    	X = p.x;
    	Y = p.y;
    	wsprintf(BUF, "Left button is down at X: %d and Y: %d", X, Y);
    	my_string = BUF;
    	InvalidateRect(NULL, 0);
    }
    ......
    ......
    
    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)
    	ON_WM_PAINT()
    	ON_WM_RBUTTONDBLCLK()
    	ON_WM_LBUTTONDBLCLK()
    	ON_WM_RBUTTONDOWN()
    	ON_WM_LBUTTONDOWN()
    END_MESSAGE_MAP()
    
    CApp App;
    Thanks, folks.

  13. #13
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    I doubt that the cast is what fixed your protection error, and it shouldn't be necessary since there is an automatic conversion from char* (the return value of get()) and const char* (LPCTSTR).

    Self assignment is assigning an object to itself. A simple, contrived example is this:
    Code:
    CStr x;
    CStr& y = x; // reference to same object.
    x = y;

  14. #14
    Registered User
    Join Date
    Oct 2005
    Posts
    271
    I'm failing to understand the difference between that and stating it this way:

    Code:
    CStr x;
    CStr* y;
    y = &x;
    In my case, x and *y refer to the same thing. Isn't the only difference that y doesn't need a dereferencing operator in your case? Then again, maybe there will arise a situation where I need two variables to instantiate the same object (that sounds weird).

    But about the comment before.
    Also your assignment operator function should probably handle self assignment.
    I tried self assignment with my present class as it is (with daved's code) and it worked fine. Does this mean that I somehow inadvertently created a self-assignment operator, or does it mean that I'm not getting an error out of pure luck? And why do I need a self-assignment operator?

  15. #15
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    In your example, you are assigning pointers. In the third line, &x returns the address of x, which is just a pointer to x. Then you are assigning that pointer to the pointer y. This is allowed, but it doesn't call the assignment operator (you are not making a copy of the object, just a pointer).

    In my example, you are making a copy of the object using the regular assignment operator. It just so happens that you are copying an object to itself. This might happen more easily with function parameters or something like that. It might not be occurring in your program, but it is just good practice to make sure your code works if somebody does do that. You just have to check if (this == &c) in your assignment operator, and if it is true, just skip the rest of the code. You don't have to add any additional operators.

    It was probably pure luck that it didn't cause a problem when you tried my code.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 8
    Last Post: 04-25-2008, 02:45 PM
  2. Inheritance Hierarchy for a Package class
    By twickre in forum C++ Programming
    Replies: 7
    Last Post: 12-08-2007, 04:13 PM
  3. Strange problems with string class
    By creativeinspira in forum C++ Programming
    Replies: 10
    Last Post: 07-18-2007, 01:11 PM
  4. Linked List Help
    By CJ7Mudrover in forum C Programming
    Replies: 9
    Last Post: 03-10-2004, 10:33 PM
  5. Classes inheretance problem...
    By NANO in forum C++ Programming
    Replies: 12
    Last Post: 12-09-2002, 03:23 PM