English 中文(简体)
Which string classes to use in C++?
原标题:

we have a multi-threaded desktop application in C++ (MFC). Currently developers use either CString or std::string, probably depending on their mood. So we d like to choose a single implementation (probably something other than those two).

MFC s CString is based on copy-on-write (COW) idiom, and some people would claim this is unacceptable in a multithreaded environment (and probably reference to this article). I am not convinced by such claims, as atomic counters seem to be quite fast, and also this overhead is somehow compensated by a reduction in memory re-allocations.

I learned that std::string implementation depends on compiler - it is not COW in MSVC but it is, or was in gcc. As far as I understood, the new C++0x standard is going to fix this by requiring a non-COW implementation and resolve some other issues, such as contiguous buffer requirements. So actually std::string looks not well defined at this point...

A quick example of what I don t like about std::string: no way to return a string from a function without excessive re-allocations (copy constructor if return by value, and no access to internal buffer to optimize that so "return by reference" e.g. std::string& Result doesn t help). I can do this with CString by either returning by value (no copy due to COW) or passing by reference and accessing the buffer directly. Again, C++0x to the rescue with its rvalue references, but we are not going to have C++0x in the nearest feature.

Which string class should we use? Can COW really become an issue? Are there other commonly used efficient implementations of strings? Thanks.

EDIT: We don t use unicode at the moment, and it is unlikely that we will need it. However, if there is something easily supporting unicode (not at the cost of ICU...), that would be a plus.

最佳回答

I would use std::string.

  • Promote decoupling from MFC
  • Better interaction with existing C++ libraries

The "return by value" issue is mostly a non-issue. Compilers are very good at performing Return Value Optimization (RVO) which actually eliminates the copy in most cases when returning by value. If it doesn t, you can usually tweak the function.

COW has been rejected for a reason: it doesn t scale (well) and the so-hoped-for increase in speed has not been really measured (see Herb Sutter s article). Atomic operations are not as cheap as they appear. With mono-processor mono-core it was easy, but now multi-core are commodity and multi-processors are widely available (for servers). In such distributed architectures there are multiple caches, that need be synchronized, and the more distributed the architecture, the more costly the atomic operations.

Does CString implement Small String Optimization ? It s a simple trick that allows a string not to allocate any memory for small strings (usually a few characters). Very useful because it turns out that most strings are in fact small, how many strings in your application are less than 8-characters long ?

So, unless you present me a real benchmark which clearly shows a net gain in using CString, I d prefer sticking with the standard: it s standard, and likely better optimized.

问题回答

Actually, the answer may be "It depends". But, if you are using MFC, IMHO, CString usage would be better. Also, you can use CString with STL containers also. But, it will lead to another question, should I use stl containers or MFC containers with CString? Usage of CString will provide agility to your application for example in unicode conversions.

EDIT: Moreover, if you use WIN32 api calls, CString conversions will be easier.

EDIT: CString has a GetBuffer() and regarding methods that allow you to modify buffer directly.

EDIT: I have used CString in our SQLite wrapper, and formatting CString is easier.

    bool RS::getString(int idx, CString& a_value) {

//bla bla

        if(getDB()->getEncoding() == IDatabase::UTF8){
            a_value.Format(_T("%s"), sqlite3_column_text(getCommand()->getStatement(), idx));
        }else{
            a_value.Format(_T("%s"), sqlite3_column_text16(getCommand()->getStatement(), idx));
        }
        return true;
}

I don t know of any other common string implementations- they all suffer from the same language limitations in C++03. Either they offer something specific, like how the ICU components are great for Unicode, they re really old like CString is, or std::string trumps them.

However, you can use the same technique that the MSVC9 SP1 STL uses- that is, "swaptimization", which is the most hilariously named optimization ever.

void func(std::string& ref) {
    std::string retval;
    // ...
    std::swap(ref, retval); // No copying done here.
}

If you rolled a custom string class that didn t allocate anything in it s default constructor (or checked your STL implementation), then swaptimizing it would guarantee no redundant allocations. For example, my MSVC STL uses SSO and doesn t allocate any heap memory by default, so by swaptimizing the above, I get no redundant allocations.

You could improve performance substantially too by just not using expensive heap allocation. There are allocators designed for temporary allocations, and you can replace the allocator used in your favourite STL implementation with a custom one. You can get things like object pools from Boost or roll a memory arena. You can get tenfold better performance compared to a normal new allocation.

I would suggest making a "per DLL" decision. If you have DLLs depending heavily on MFC (for example, your GUI layer), where you need a lot of MFC calls with CString parameters, use CString. If you have DLLs where the only thing from MFC you are going to use would be the CString class, use std::string instead. Of course, you will need conversion function between both classes, but I suspect you have already solved that issue.

I say always go for std::string. As mentioned, RVO and NVRO will make returning by copies cheap, and when you do end up switching to C++0x eventually, you get a nice performance boost from move semantics, without doing anything. If you want to take any code and use it in a non-ATL/MFC project, you can t use CString, but std::string will be there, so you ll have a much easier time. Finally, you mentioned in a comment you use STL containers instead of MFC containers (good move). Why not stay consistent and use STL string too?

I would advise using std::basic_string as your general string template base unless there is a good reason to do otherwise. I say basic_string because if you are handling 16-bit characters you would use wstring.

If you are going to use TCHAR you should probably define tstring as basic_string and may wish to implement a traits class for it too to use functions like _tcslen etc.

std::string is usually reference counted, so pass-by-value is still a cheap operation (and even more so with the rvalue reference stuff in C++0x). The COW is triggered only for strings that have multiple references pointing to them, i.e.:

std::string foo("foo");
std::string bar(foo);
foo[0] =  m ;

will go through the COW path. As the COW happens inside operator[], you can force a string to use a private buffer by using its (non-const) operator[]() or begin() methods.





相关问题
Simple JAVA: Password Verifier problem

I have a simple problem that says: A password for xyz corporation is supposed to be 6 characters long and made up of a combination of letters and digits. Write a program fragment to read in a string ...

Case insensitive comparison of strings in shell script

The == operator is used to compare two strings in shell script. However, I want to compare two strings ignoring case, how can it be done? Is there any standard command for this?

Trying to split by two delimiters and it doesn t work - C

I wrote below code to readin line by line from stdin ex. city=Boston;city=New York;city=Chicago and then split each line by ; delimiter and print each record. Then in yet another loop I try to ...

String initialization with pair of iterators

I m trying to initialize string with iterators and something like this works: ifstream fin("tmp.txt"); istream_iterator<char> in_i(fin), eos; //here eos is 1 over the end string s(in_i, ...

break a string in parts

I have a string "pc1|pc2|pc3|" I want to get each word on different line like: pc1 pc2 pc3 I need to do this in C#... any suggestions??

Quick padding of a string in Delphi

I was trying to speed up a certain routine in an application, and my profiler, AQTime, identified one method in particular as a bottleneck. The method has been with us for years, and is part of a "...

热门标签