Is there ASCII equivalent of CStringArray?
Like there is CStringA and CStringW where CString is generic form, is there ASCII form for CStringArray? I have some strings which are ascii by design and I would like the CStringArray to store them in a Unicode build. Can I force CStringArray to be on ASCII strings? Is this possible?
Re: Is there ASCII equivalent of CStringArray?
I'm just guessing but most likely there is no CStringArrayA/W since it shouldn't really need to bother about the content of the object it's holding. It's an intresting question though. Have you tried to mix MBCS and UNICODE in a CStringArray?
Re: Is there ASCII equivalent of CStringArray?
Try CArray<CStringA, CStringA&>
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
S_M_A
I'm just guessing but most likely there is no CStringArrayA/W since it shouldn't really need to bother about the content of the object it's holding. It's an intresting question though. Have you tried to mix MBCS and UNICODE in a CStringArray?
I only only to store CStringA strings but since its unicode build the strings will store as CStringW.
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
Igor Vartanov
Try CArray<CStringA, CStringA&>
I am already using CStringArray class, in fact I have derived my class from CStringArray with added functions and would like to keep is as based on CStringA.
Re: Is there ASCII equivalent of CStringArray?
Yes I realized that now.
Have you tried Igors suggestion?
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
zspirit
I am already using CStringArray class, in fact I have derived my class from CStringArray with added functions and would like to keep is as based on CStringA.
Then you need to change your type (Igor's suggestion) and/or change your design. CStringArray is based on CString, and whatever CString happens to be, well, that's it.
A CStringA and a CStringW are two different types altogether, regardless of how similar they are in what their purpose is. You might as well have said one type is "double" and the other type is "Widget" -- two different types.
Therefore to have containers of these types, you need to create two different container classes, one to store only CStringA and the other to store only CStringW. You can't finagle one container, CStringArray, to make it do both or have it flip over and store the other CString type in the same application.
Another question -- why did you need to derive from CStringArray?
Regards,
Paul McKenzie
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
Paul McKenzie
Then you need to change your type (Igor's suggestion) and/or change your design. CStringArray is based on CString, and whatever CString happens to be, well, that's it.
I have stuck to one CStringArray class which only handle CString type so it's still generic but I am manually converting them to CStringA when I need them. So far this looks good.
Quote:
Originally Posted by
Paul McKenzie
Another question -- why did you need to derive from CStringArray?
I mostly have search related functions added to my CStringArray derived class. Since I do these searches on the list, I prefer to delegate it to my class rather than in various .cpp files. For example look if particular string exist in the list, get index by string name (if it exist) etc.
Now my main problem (new) is how to let my CStdioFile file write CStringA instad of CStringW with unicode. My project is going to be unicode but I still want the file data to be ascii. CStdioFile::WriteString() now writes unicode strings which changes the format.
Is there any way I can force CStdioFile to be based on CStringA only? Can I use any tricks like what Igor suggested here? That would be very useful!
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
zspirit
I mostly have search related functions added to my CStringArray derived class. Since I do these searches on the list, I prefer to delegate it to my class rather than in various .cpp files.
Why would it be in various CPP files? You could have created a namespace, call it CStringArrayUtils or something like that, and place all your functions there.
Code:
// In some header file
namespace CStringArrayUtils
{
int SearchCStringArray(const CStringArray& arr, const CString& searchItem);
int SearchCStringArrayAnotherWay(const CStringArray& arr, const CString& searchItem);
// etc...
}
and then in one C++ module:
Code:
namespace CStringArrayUtils
{
int SearchCStringArray(const CStringArray& arr, const CString& searchItem)
{
// implementation
}
int SearchCStringArrayAnotherWay(const CStringArray& arr, const CString& searchItem)
{
// implementation
}
}
Then you call it like this:
Code:
#include "CStringArrayUtils.h"
//...
CStringArray a1;
//...
int where = CStringArrayUtils::SearchCStringArray(a1, "abc123");
More than lilkely, you could have just use std::find_if() or std::find() to return CStrings in a CStringArray based on certain criteria, and not need to derive anything or write your own search logic.
Also in your case, the derived object is exactly the same as the base object, but with the only difference being actions are added to the derived class with (assuming) no overrides of any virtual functions from the base class. That's a very weak case for public derivation, IMO.
Regards,
Paul McKenzie
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
Paul McKenzie
Also in your case, the derived object is exactly the same as the base object, but with the only difference being actions are added to the derived class with (assuming) no overrides of any virtual functions from the base class. That's a very weak case for public derivation, IMO.
Right but my thinking was that since I a extending the class with more functionality, deriving it would make sense. I like the syntax of how my derived instance contains all the functionality of base class plus additional search functions, so the code can be a tad cleaner. You have raised interesting point and this can be handy but if CStringArrayUtils only apply to CStringArray than why not package them together. Any particular reason why is it a weak case? Thanks!
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
zspirit
I like the syntax of how my derived instance contains all the functionality of base class plus additional search functions,
This is really an issue of class design, but here is what many will say to you:
You shouldn't use public derivation to save typing or to make syntax "neater".
Take your example -- it's similar to creating a class called "Dog", and then deriving classes called "DogPlayDead", "DogFetchStick", etc. Is a DogFetchStick a different type of Dog, or is it the exact same "Dog" but with different attributes? If it's the former, then yes, inheritance would be justified. But if it's the latter, then those are actions performed on, with, or by a Dog, and not a different category of Dog. This does not justify inheritance IMO.
Instead a library of routines that works with the Dog class would be created, or if not that, composition (a class that contains a Dog object) or something similar to composition, such as namespace usage.
If you posted one of your "search" functions, I can probably show you how you unnecessarily coupled the search to your CStringArray, and uncoupling the search from CStringArray makes the code more flexible. For example, let's say you do come up with the CStringArrayA class, but you want the same search facilities for both CStringArray and CStringArrayA. With your current design, you now have to duplicate the search code in both classes, with the only difference being the type.
Regards,
Paul McKenzie
Re: Is there ASCII equivalent of CStringArray?
Here are some of the functions that I have added to my CStringArrayEx. This includes minus (-) operator overloading which subtracts one list from the other, it returns an array list which is a difference between the two lists.
Code:
int CStringArrayEx::GetIndex(const CString &strToSearch) const
{
int count = GetSize();
for (int i=0; i <count; i++)
{
CString strCur = GetAt(i);
strCur.TrimRight();
if (strCur == strToSearch)
return i;
}
return -1; // item not found
}
BOOL CStringArrayEx::FindNoCase(const CString &strToSearch) const
{
int count = GetSize();
for (int i=0; i <count; i++)
{
#ifdef _DEBUG
CString str = GetAt(i);
#endif
if(GetAt(i).CompareNoCase(strToSearch) == 0)
return TRUE;
}
return FALSE;
}
CStringArrayEx CStringArrayEx::operator - (const CStringArrayEx & rhs)
{
CStringArrayEx diffList; // difference list
// number of items in source array
int srcItemCount = GetCount();
// number of items in reference array (right hand side)
int rhsItemCount = rhs.GetCount();
CString strItem;
for (int i = 0; i < rhsItemCount; i++)
{
strItem = rhs.GetAt(i);
// if rhs string item is not in the source array, add it to the difference list
if ( !FindNoCase( strItem ) )
diffList.Add( strItem );
}
return diffList;
}
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
zspirit
IIs there any way I can force CStdioFile to be based on CStringA only? Can I use any tricks like what Igor suggested here? That would be very useful!
One way is to use use templates to specify how to read and write using CString or CStringA.
Looking at the CFile parent class, you have functions Read() and Write(). I would guess you can use these functions to write CStringA types instead of ReadString() and WriteString(). The question is how do you tell CStdioFile which one you want to use if you specify a CStringA as opposed to a CString.
I am not a user of CStdioFile, but given my short look at the class, here is an example of what I believe could be used:
Code:
template <int MaxChars=1024>
struct ANSIReaderWriterTraits
{
typedef CStringA StringType;
static void Read(CStdioFile& theFile, CStringA& theString)
{
char lpBuf[MaxChars];
theFile.Read(lpBuf, MaxChars);
theString = (LPCSTR)lpBuf;
}
static void Write(CStdioFile& theFile, const CStringA& theString)
{
theFile.Write((LPCSTR)theString, theString.GetLength());
}
};
struct UnicodeReaderWriterTraits
{
typedef CString StringType;
static void Read(CStdioFile& theFile, CString& theString)
{
theFile.ReadString( theString );
}
static void Write(CStdioFile& theFile, const CStringW& theString)
{
theFile.WriteString( theString );
}
};
class FileReaderWriter
{
CStdioFile& theFile;
public:
FileReaderWriter(CStdioFile& f) : theFile(f) { }
template <typename ReaderWriterTraits>
void ReadString(typename ReaderWriterTraits::StringType& s)
{
ReaderWriterTraits::Read(theFile, s);
}
template <typename ReaderWriterTraits>
void WriteString(const typename ReaderWriterTraits::StringType& s)
{
ReaderWriterTraits::Write(theFile, s);
}
CStdioFile& GetStdioFile() { return theFile; }
};
int main()
{
CStdioFile myFile;
FileReaderWriter rw(myFile);
CString sUnicode;
CStringA sANSI;
// write a CString
rw.ReadString<UnicodeReaderWriterTraits>( sUnicode );
// write a CStringA with default max 1024 characters
rw.WriteString<ANSIReaderWriterTraits<> >( sANSI );
}
The FileReaderWriter could be considered an adapter for CStdioFile. You construct a FileReaderWriter by supplying your CStdioFile object. Then to read or write, you specify the type of read/write you want as a template argument (the UnicodeReaderWriterTraits or the ANSIReaderWriterTraits types).
Then the function argument is the CString or CStringA you want to read/write.
I wrote this quickly, and I can bet it can be improved, shortened, made better, etc. in some way. For example, this code assumes a Unicode compile, so the code probably needs to be changed here and there to make sure the CString types are correct if MBCS compilation was used.
But the point is to illustrate that you can add flexibility to code without inheritance. The above example is a type of policy-based design. For example, I didn't have to derive further from CStdioFile to add a "WriteStringA()" function or a "ReadStringA" function. I used the existing CStdioFile class and used policies to figure out how to write CString/CStringA.
Regards,
Paul McKenzie
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
zspirit
Here are some of the functions that I have added to my CStringArrayEx.
The first two functions can be rewritten, without deriving from CStringArray. Not only that, the rewrite would work with the CStringArrayA (if it did exist, and it followed the MFC pattern) as well as CStringArray.
Code:
#include "stdafx.h"
#include <algorithm>
template <typename T>
struct IndexSearcher
{
typedef T CStringType;
T m_srch;
IndexSearcher(const T& srch) : m_srch(srch) { }
bool operator()(const T& s) const
{
T strCur = s;
strCur.TrimRight();
return ( strCur == m_srch );
}
};
template <typename T>
struct CaseFinder
{
typedef T CStringType;
T m_srch;
CaseFinder(const T& srch) : m_srch(srch) { }
bool operator()(const T& s) const
{ return s.CompareNoCase(m_srch) == 0; }
};
template <typename MFCStringArrayType, typename UtilityFn>
int GetIndex(const MFCStringArrayType& arr, UtilityFn fn)
{
INT_PTR nItems = arr.GetCount();
const UtilityFn::CStringType* ptr = std::find_if(arr.GetData(), arr.GetData() + nItems, fn);
int index = (int)std::distance(arr.GetData(), ptr);
if (index >= nItems )
return -1;
return index;
}
int main()
{
CStringArray testa;
testa.Add(CString("abc123"));
testa.Add(CString("xxxxxx"));
int in1 = GetIndex(testa, IndexSearcher<CString>(CString("xxxxxx")));
int in2 = GetIndex(testa, CaseFinder<CString>(CString("xxx1xx")));
/* If CStringArrayA existed, this would (or should) compile:
CStringArrayA testb;
testb.Add(CStringA("abc123"));
testb.Add(CStringA("xxxxxx"));
in1 = GetIndex(testb, IndexSearcher<CStringA>(CStringA("xxxxxx")));
in2 = GetIndex(testb, CaseFinder<CStringA>(CStringA("xxx1xx")));
*/
}
This can be improved by putting the template in a header file and just including the header wherever you want to call GetIndex. Also, a namespace wrapped around it makes it self-contained.
Note that there is only one function, GetIndex, and one set of searcher/index finder classes, and not multiple code copies of such. With your current design, if you were to come up with CStringArrayA, you would need to duplicate the code in both classes. With the above setup, no such duplication occurs if CStringArrayA existed.
In general, your answers (at least the ones I've given) requires the usage of templates as a way to communicate what you want to do given a certain type at compile time.
As to your operator overload of - is concerned, is your CStringArray sorted? If so, that entire code for operator - could have been replaced with std::set_difference(). Right now, your operator - is of n^2 complexity, as you have to potentially go through the list n*n times (where n is the number of items in the list). Imagine if there are 1,000 items -- that's a worst case scenario of 1,000,000 comparisons, add to that, you're comparing strings, which is inherently not efficient. If that array were sorted, then you have set_difference() or equivalent to reduce the complexity.
Regards,
Paul McKenzie
Re: Is there ASCII equivalent of CStringArray?
Here is a final version of the functions. Please note that everything posted here is not the greatest, final version that someone could come up with. Again, this was done quickly without any peer-review or extensive testing for flaws. But hopefully it gets the main idea across:
Here is CStringArrayUtils.h:
Code:
#pragma once
#include "stdafx.h"
#include <algorithm>
namespace CStringArrayUtils
{
template <typename T>
struct IndexSearcher
{
T m_srch;
IndexSearcher(const T& srch) : m_srch(srch) { }
bool operator()(const T& s) const
{
T strCur = s;
strCur.TrimRight();
return ( strCur == m_srch );
}
};
template <typename T>
struct CaseFinder
{
T m_srch;
CaseFinder(const T& srch) : m_srch(srch) { }
bool operator()(const T& s) const
{ return s.CompareNoCase(m_srch) == 0; }
};
template <typename CStringArrayType, typename UtilityFn>
int GetIndex(const CStringArrayType& arr, UtilityFn fn)
{
struct TempClass : public CStringArrayType
{ typedef CStringArrayType::BASE_TYPE STRING_TYPE; };
typedef TempClass::STRING_TYPE CStringType;
INT_PTR nItems = arr.GetCount();
const CStringType* ptr = std::find_if(arr.GetData(), arr.GetData() + nItems, fn);
int index = (int)std::distance(arr.GetData(), ptr);
if (index >= nItems )
return -1;
return index;
}
template <typename CStringArrayType>
struct DifferenceCreator
{
// Get the underlying CString type from the CStringArrayType
// This kludge is necessary, since MFC has the type as protected
struct TempClass : public CStringArrayType
{ typedef CStringArrayType::BASE_TYPE STRING_TYPE; };
typedef TempClass::STRING_TYPE CStringType;
const CStringArrayType* m_arr;
CStringArrayType* m_out;
DifferenceCreator(const CStringArrayType* arr, CStringArrayType* out) : m_arr(arr), m_out(out) {}
void operator()(const CStringType& str) const
{
if ( GetIndex(*m_arr, CaseFinder<CStringType>( str ) ) == -1 )
m_out->Add( str );
}
};
template <typename CStringArrayType>
void GetDifference(const CStringArrayType& arr1, const CStringArrayType& arr2, CStringArrayType& out)
{
// get difference for entire array
std::for_each(arr2.GetData(), arr2.GetData() + arr2.GetCount(),
DifferenceCreator<CStringArrayType>(&arr1, &out));
}
}
Here is a sample:
Code:
#include "stdafx.h"
#include "CStringArrayUtils.h"
using namespace CStringArrayUtils;
int main()
{
CStringArray testa, testb, testc;
testa.Add(CString("abc123"));
testa.Add(CString("xxxxxx"));
testa.Add(CString("x3"));
testb.Add(CString("abc123"));
testb.Add(CString("xxx2xxx"));
GetDifference(testa, testb, testc); // testc now has the difference
CString findstr1 = _T("xxxxxx");
CString findstr2 = _T("xxxx1x");
int in1 = GetIndex(testa, IndexSearcher<CString>( findstr1 ));
int in2 = GetIndex(testa, CaseFinder<CString>( findstr2 ));
}
The GetDifference is now a templated function, with the template dependent on the CStringArray type (either CStringArray, or CStringArrayA).
I changed a few things so that I could get the CString type from the CStringArray type. I don't know why MFC hides the BASE_TYPE typedef as protected, but that temporary struct I have in those classes are done only to get the BASE_TYPE of the CStringArray available to the functions. If MFC had made the BASE_TYPE typedef public, those extraneous three lines of code setting up a struct would not be necessary. I tested with VS 2008, so I don't know if the typedef has been moved to public in VS 2010.
Note that GetDifference() uses for_each and the DifferenceCreator function object, which itself uses GetIndex() and CaseFinder() to determine if the string is in the other array. Also note something surprising -- no for() loops in the entire code base.
All of this would be great if there was actually a CStringArrayA that follows the pattern of CStringArray, but I don't think it would be that difficult if you created such a class.
Code:
class CStringArrayA
{
CArray<CStringA, CStringA&> m_arr;
public:
typedef CStringA BASE_TYPE;
void Add(const CStringA& str) { m_arr.Add(str); }
// etc...
};
Then you continue on from there adding the rest of the functions to this class. Then you can use this class in the code above.
But the real point I'm showing you is that if there was the other CStringArrayA class, you need not have to duplicate all of these derived functions in that class, as the code would work for CStringArray, CStringArrayA, or any other class that has the same "pattern" as the CStringArray MFC class. As you can see, no derivation was done, the code is flexible in terms of supporting any CStringArray type that follows the MFC CStringArray pattern, and it is one area of the code (the header file).
If you want to see a small example of this very same concept I showed you, look at CStringT from ATL. You see that the arguments to that template describe how CStringT is supposed to behave based on compile-time type information.
Regards,
Paul McKenzie
Re: Is there ASCII equivalent of CStringArray?
I end up resolving this issue by using just unicode version of CStringArray after all. I liked the CStringArrayUtils namespace approach to extend the function but I am still not sure if I would prefer this over deriving from the class for any particular reason!? Your approach gives me a good insight but at the same time is more complex even if we remove the templates. For example my difference function look like this and I like the fact it is invoked just by - operator so the main code looks clean.
Code:
CStringArrayEx CStringArrayEx::operator - (const CStringArrayEx & rhs)
{
CStringArrayEx diffList; // difference list
// number of items in reference array (right hand side)
int rhsItemCount = rhs.GetCount();
CString strItem;
for (int i = 0; i < rhsItemCount; i++)
{
strItem = rhs.GetAt(i);
// if rhs string item is not in the source array, add it to the difference list
if ( !FindNoCase( strItem ) )
diffList.Add( strItem );
}
return diffList;
}
Re: Is there ASCII equivalent of CStringArray?
Quote:
Originally Posted by
zspirit
I end up resolving this issue by using just unicode version of CStringArray after all. I liked the CStringArrayUtils namespace approach to extend the function but I am still not sure if I would prefer this over deriving from the class for any particular reason!?
You can do anything in C++, I'm saying that experts will tell you that deriving just for the sake of adding attributes is not what public inheritance was designed for. See my Dog class example in my previous post.
Quote:
Your approach gives me a good insight but at the same time is more complex even if we remove the templates.
Well what I wrote is C++ -- if it's complex to you, then maybe you need more experience in C++, as that code should be understood by an intermediate to advanced C++ programmer.
Also, when classes are designed, complexity of the internals shouldn't be a concern to the user. It is the usage of the classes and how flexible they are that matter. Classes are made so that they are the most flexible, easy to use, and difficult to use incorrectly. To accomplish this, the internals of such classes must be complex or somewhat complex. Have you seen the MFC internals, or STL internals, or any class internals? Of course it's going to be complex internally -- that's the only way to achieve flexibility, ease of use, and cut down on user mistakes.
Quote:
For example my difference function look like this and I like the fact it is invoked just by - operator so the main code looks clean.
You're showing this code now, but how would this have worked if you did do as originally stated, and that is to have a CStringArrayA class? You would then need to duplicate that entire code. That's the point I was attempting to make with my example.
Also, experts will tell you that you should write a concrete, non-operator overloaded version of operator -. It isn't intuitive as to what subtraction is supposed to do when you have an array of strings. A function called GenerateDifference() or something of that nature describes in words what is being done. A programmer would also be expecting operator -= to be overloaded if operator - is overloaded.
When I write programs, I make it a rule that all base functionality is written concretely (no operator overloaded functions doing the real work). If I now need to overload operators, the overloaded operators call the concrete functions to do the work.
Regards,
Paul McKenzie