Note: This article was originally written in Chinese by Xiao Feng Guan. I translated this article into English.
In C++11, with the introduction of rvalue reference, the performance of the std::string class has been improved greatly. Besides, the std::string class has included many new APIs, including the stoi/stol/sto member functions ,as well as the std::to_string global function. These two kinds of functions are very useful in formatted I/O in C++11. This article will show how the atoi (ASCII to integer) and itoa (integer to ASCII) conversions are implemented in C, C++98, and C++11 separately.
Commonly in C code, the library function atoi (stdlib.h) is used to do the atoi conversion. For example:
int num = atoi(cstr);
where cstr is a string whose type is char* or const char*. The function returns a decimal integer that is interpreted from the content of the string. This atoi function has the similar behavior as the strtol function in stdlib.h. For example:
int num = strtol(cstr, NULL, 10);
strtol's third parameter specifies the radix whose value is between 2 and 36 inclusively. But strtol will set the global errno when an error occurs while atol would not.
In C, the sprintf function is used to implement itoa (integer to ASCII). For example:
sprintf(buf, "my data is %d", myint);
where %d is the format string that controls the output format. Used with variadic function feature in C language, sprintf can get formatted I/O output as expected.
In summary, atoi/itoa conversions in C have the following features:
- atoi does not check the validity of input strings, which leaves such responsibilities to programmers.
- strtol checks the validity of strings. Further, it can set POSIX errno. However, it burdens programmers the responsibilities to check errno for each call to strtol.
- sprintf does not manage memory at all. Meanwhile, it is suggested to use snprintf instead of sprintf, because snprintf checks memory boundary, which makes programs free of stack overflow.) Consequently, programmers need to take care of memory management by themselves.
- Similar to printf, sprintf does not check parameter types (Varadic function cannot do anything with type matching). This causes errors cannot be exposed until run time. But this issue is easy to be detected and fixed, comparing to the first three issues that have high possibility to break down programs after very long execution time.
The atoi and itoa conversions in C are not very satisfying to programmers, because programmers need to deal with invalid input and exceptions to avoid worst case. On the other hand, these functions are straightforward and easy to use. So they are not rare in C++ code.
In C++98, the atoi and itoa conversions can be implemented with stream template class in the new C++ standard library. It is noteworthy that although strings can still be stored in c-arrays in C++ code, it is suggested to use the std:: string class instead. This is because that std:: string has automatic memory management and its member functions can throw exceptions, which better fits into C++ philosophy. In C++ code, formatted I/O are mostly done with stream class. std::stringstream is one of such stream classes for std::string. Global (overloaded) operator << and operator >> is feasible to do atoi and itoa conversions. For example:
oss << 15 << " is int, " << 3.14f << " is float." << endl;
cout << oss.str();
where oss is a string stream object that does itoa conversion straightly.
Here is another example:
istringstream iss("12 14.1f");
iss >> a >> b;
cout << a << " " << b << endl;
where iss is a string stream object that can be used for atoi conversions.
Technically, the design of std::stringstream sounds good. As a standard component of ISO C++ library, it relieves programmers of handling exceptions. If an exception is thrown but not caught by any catch block, std::terminate is invoked to terminate the program directly. And as described above, stringstream is always associated to a string object that can do automatic memory management, which further relieves programmers with the pain of memory management. That is an evolution from C programming.
Although std::stringstream brings in evolution, sometimes programmers still prefer to use atoi and itoa conversions over streams. This might due to the following two reasons:
1. Compared with functions, a std::stringstream object is associated with a std::string object. It is always initialized from a std::string object (for example, iss("12 14.1f")), and is always converted to a std::string object (for example, oss.str()) when programmer want to do something with the data. Thus it is an indirect concept just for I/O. However, intuitively, programmers who are new to the language is likely to write an invalid code like:
string a << 12 << " is int";
2. The inconvenience. . To use sprintf, programmers can get formatted I/O easily with the help of manual (commonly the escape character is looked up most). However, stream object maintains a state machine. That means, programmers need to care about the stream status of the last state when write anything new to the stream. So even without compile-time type check, exception handling, or memory management in C++ programs, some programmers still prefer sprintf. (For sprintf, an alternative solution is boost::format, which may fit cross-platform development)
So to summarize, although std::stringstream is a powerful C++ approach to implement atoi and itoa conversions, its usage is limited in real scenarios. Because it takes more time to grasp and its usability is limited in formatted I/O.
In C++11, global functions, such as std::to_string, std::stoi/stol/stoll are introduced to implement atoi/itoa conversions conveniently. For example:
s += to_string(12) + " is int, ";
s += to_string(3.14f) + " is float.";
cout << s << endl;
where to_string can do type conversion according to the parameter type.
Here is another example:
int i = stoi(s);
cout << i << endl;
This example can implement atoi conversion successfully, because the stoi function has the advantages as a C++ standard library function: type conversion, exception handling, and automatic memory management.
std::to_string can also be used in string concatenation. The string concatenation performance of C++98 is lower than that of C. With the introduction of rvalue reference into C++11, the string concatenation performance has been greatly improved. But std::to_string still has its limitation when working with floating-point numbers. For example, std::to_string cannot specify the number of decimal digits. After all, the most advantage of std::to_string is ease of use: programmers can do itoa conversion safely and effectively without the need to define a std::stringstream object or worry about memory management.
The std::stoi/stol/stoll functions are easy to use, because they only perform numeric conversions. The return type of operator>> is std::stringstream& and can obtain multiple converted values in one statement, but it has to be associated to a std::stringstream object. So in some simple scenarios, std::stoi/stol/stoll are a good choices and can work well.