Developer

Implement these quick tips to make better use of the STL in C++

The Standard Template Library (STL) enables you to make your C++ code more reusable and efficient. This pair of tips will help you tweak the STL to avoid some common errors.


By John Torjo

The Standard Template Library (STL) provides algorithms and containers that use templates to implement a reusable and extensible code base. Here's a pair of tips on formulating functions and avoiding off-by-one errors that will help you implement the STL more efficiently.

Background reading
Before charging into this subject, you may want to brush up on STL by reading "Reap the benefits of the C++ Standard Template Library."

Converting to and from strings
Many Java fans criticize STL for not including some fairly obvious utility functions in the definition of std::string. But these functions were excluded from the definition because you can easily create them, if necessary. For instance, this to_string function converts any type to a string:
template< class T>
    inline std::string to_string( const T & Value)
{
    std::stringstream streamOut;
    streamOut << Value;
    return streamOut.str( );
}

//  specialization for string.
template< >
    inline std::string to_string( const std::string & Value)
{
    return Value;
}


This function works for any built-in type. To work for a class, the class must implement the << operator.

Of course, it's just as easy to implement its reverse function, as illustrated by the following code snippet:
template< class T>
    inline T from_string( const std::string & ToConvert)
{
    std::stringstream streamIn( ToConvert);
    T ReturnValue = T( );
    streamIn >> ReturnValue;
    return ReturnValue;
}


The from_string function converts a string into a type—for instance, it can convert a string into a number. If the input is a valid value, the function converts and returns it. Otherwise, the function returns the default value for the given type: T(), the default constructor. Again, this works for any built-in type. It also works for any class with a default constructor that has implemented the >> operator.

Note that the from_string function assumes that there is only one value in the string you’re trying to convert. Any additional values will be ignored. If you know there is more data and you need it, don’t use the from_string function. Read from streams (directly) instead.

This sample shows how you can use the preceding two functions:
    int n = 4;
    std::string str = to_string( n);        // "4"
    long l = 534587;
    str = to_string( l);                    // "534587"
    n = from_string< int>( "a");            // 0
    int nTest = from_string< int>( "5744"); // 5744
    int nTest2 = from_string< int>( "57y4"); // 57
    int nTest3 = from_string< int>( "5743 65"); // 5743; 65 ignored !!!
    return 0;


Avoid off-by-one errors
Bugs often arise because you either forget to check the last element in an array or, even more critical, you read/write past the end of an array, which is an access violation. Such situations usually occur when there aren't team-programming standards.

Here is one way to avoid off-by-one errors. Use the [start_of_seq, end_of_seq) type of loop. This means that you include all elements starting from start_of_seq up to but not including end_of_seq. It also accounts for the empty sequence, [start, start). This is a guideline inspired from the STL. Here's how STL iterators work: You always have container.begin(), which contains start_of_seq, and container.end(), which contains end_of_seq (past the end iterator).

Iterating a sequence as described above is quite straightforward. For example:
StringsArray::const_iterator itFirst = astrNames.begin();
StringsArray::const_iterator itLast = astrNames.end();
while ( itFirst != itLast)
{
    std::cout << *itFirst << std::endl;
    ++ itFirst;
}


However, you can't always use STL. Developers often have to deal with old code or APIs. To follow this convention on any loop, try to avoid the <= operator. When accessing an array by index, this is simple:
for ( int idx = 0; idx < nNamesCount; ++idx)
{
    std::cout << astrNames[ idx] << std::endl;
}


If you know the first index and the last index from an array, try to obtain the index of the past-the-end element:
int idxStart = idxFirst;
int idxEnd = idxLast + 1;
for ( int idx = idxStart; idx < idxEnd; ++idx)
{
    std::cout << astrNames[ idx] << std::cout;
}


You can also apply this to pointers:
const std::string * pStrStart = GetFirstName();
const std::string * pStrEnd = GetLastName() + 1;
while ( pStrStart < pStrEnd)
{
    std::cout << pStrStart->c_str() << std::endl;
}


Write for Builder.com
If you're interested in contributing C++ content to Builder.com, contact the editor.

 

Editor's Picks