Expensive std::string::c_str() use in a std::string operation

A string operation uses the output of an std::string::c_str method, resulting in inefficient code

Description

This defect occurs when a string operation is performed by using the C-string pointer obtained from std::string::c_str. For instance, this checker is raised when:

  • A new std::string or std::wstring is implicitly or explicitly constructed from the output of the corresponding c_str. This situation arises when a function expecting a const reference to the string encounters a const char* instead.

  • A new copy of a string object is created explicitly from the output of its c_str function. Using the copy constructor is the more efficient way of copying the string object.

  • Certain std::string member functions are invoked by using the output of std::string::c_str. Flagged functions include replace, append, assign, compare, and find. Using an std::string object directly to invoke std::string member functions is more efficient.

  • A user-defined function that is overloaded to accept either of the const char* and const std::string arguments is invoked by using a C-string pointer. It is more efficient to invoke the std::string overload of such a function.

Risk

It is expensive and inefficient to use the C-string output of the std::string::c_ctr method when you can use an std::string object instead. An std::string object contains the length of the string. When you use the C-string output of the std::string::c_str method instead of an std::string object, the constructor determines the length of the C-string by a linear search, resulting in inefficient code. Using std::string::c_str is also often unnecessary. Consider this code:

void set_prop1(const char* str);
void set_prop2(const std::string& str);
void foo( std::string& str){
	//...
	set_prop1(str.c_str()); // Necessary
	//...
	set_prop2(str.c_str()); // Inefficient	
}
The function foo calls two different functions. Because the function set_prop1 requires a C-string as the input, using the str.c_str function is necessary to form the input to set_prop1. The function set_prop2 takes an std::string as an input. Instead of directly using str as an input to set_prop2, str.c_str is used, perhaps as a copy-paste mistake. The compiler implicitly constructs a new std::string object, which is identical to str, by using the output of str.c_str. Constructing a new std::string object in this case is unnecessary and inefficient. Because this code compiles and functions correctly, this inefficient code might not be noticed.

Fix

To fix this defect, replace calls to std::string::c_str by std::string. Consider this code:

void set_prop1(const char* str);
void set_prop2(const std::string& str);
void foo( std::string& input){
	//...
	set_prop1(str.c_str()); // Necessary
	//...
	set_prop2(str); // Efficient	
}
Using str instead of str.c_str as input to set_prop2 makes the code more efficient and fixes the defect.

Performance improvements might vary based on the compiler, library implementation, and environment that you are using.

Examples

expand all

#include <string>
#include <utility>

class A
{
public:
	A( char const* );
	char const* c_str() const;
};
void CppLibFuncA(const std::string&);
void CppLibFuncB(std::string &&);
void bar( A const& );
std::string make_string();
bool contains( std::string const& str1, std::string const& str2 )
{
	return str1.find( str2.c_str() ) == std::string::npos;
}

void foo(const std::string& s, std::string&& rs, A& other){
	CppLibFuncA( s.c_str() ); 
	CppLibFuncB( std::move( rs ).c_str() ); 
	CppLibFuncA( make_string().c_str() ); 
	bar( other.c_str() ); 
	if(contains(s,make_string())){
		//...
	}
}

In this example, Polyspace® flags the implicit use of std::string::c_str to construct an std::string object when calling functions.

  • The function CppLibFuncA takes a const std::string& as input. When the function CppLibFunc is called by using s.c_str(), the compiler cannot pass a reference to s to the function. Instead, the compiler implicitly constructs a new std::string object from the C-string pointer and passes the new object to the function,which is inefficient. Polyspace flags the call to std::string::c_str.

  • Because calling CppLibFuncB by using the output of std::string::c_str also implicitly constructs a new str::string object, Polyspace flags the call to std::string::c_str.

  • A call to the function bar is not flagged because a const char* is not implicitly converted to a new std::string object.

  • Polyspace does not flag the use of std::string::c_str in operations other than construction. The call to std::string::find(), where the output of std::string::c_str is used instead of an std::string object, is not flagged.

Correction

To fix this issue, avoid implicit construction of new std::string objects from the outputs of the std::string::c_str function. You can omit the call to std::string::c_str in function calls and use the existing std::string object.

#include <string>
#include <utility>
void CppLibFuncA(std::string const &);
void CppLibFuncB(std::string &&);
std::string make_string();
bool contains( std::string const& str1, std::string const& str2 )
{
	return str1.find( str2 ) == std::string::npos;
}
void foo(std::string const & s, std::string&& rs){
	CppLibFuncA( s ); 
	CppLibFuncB( std::move( rs ) ); 
	CppLibFuncA( make_string()); 
	if(contains(s,make_string())){
		//...
	}	
}

Fix calls to the functions CppLibFunc, CppLibFuncB, and CppLibFuncC by removing the call to std::string::c_str and instead using the existing std::string objects as input.

#include <string>
#include <utility>
std::string make_string(void);
void bar(const std::string& s){
	std::string s1 = s.c_str(); // Inefficient
	std::string s2 = make_string();
	s2.append(s1.c_str());
}

In this example, Polyspace flags the explicit use of std::string::c_str to copy the std::string object s to s1. The content of s1 is also appended to s2 by using s1.c_str(). Polyspace flags the use of the C-string in the std::string::append() function.

Correction

To fix this issue, avoid using std::string::c_str when you can use an std::string object.

#include <string>
#include <string>
#include <utility>
std::string make_string(void);
void bar(const std::string& s){
	std::string s1 = s; // Efficient
	std::string s2 = make_string();
	s2.append(s1);
}
#include <string>
#include <utility>
std::string make_string(void);
void bar( std::string& s1){
	std::string s2 = make_string();
	s1.replace(1, 1, s2.c_str());               
	s1.replace(s1.begin(), s1.end(), s2.c_str());   
	s1.append(s2.c_str());                          
	s1.assign(s2.c_str());                         
	s1.compare(s2.c_str());                        
	s1.find(s2.c_str());                            
}

In this example, Polyspace flags the explicit use of std::string::c_str to invoke member functions of the std::string class.

Correction

To fix this issue, avoid using std::string::c_str when you can use an std::string object instead.

#include <string>
#include <string>
#include <utility>
std::string make_string(void);
void bar( std::string& s1){
	std::string s2 = make_string();
	s1.replace(1, 1, s2);               
	s1.replace(s1.begin(), s1.end(), s2);   
	s1.append(s2);                          
	s1.assign(s2);                         
	s1.compare(s2);                        
	s1.find(s2);                            
	s1.find(s2); 
}
#include <string>
#include <utility>
extern void userDefined(const char *);
extern void userDefined(const std::string &);
void bar( const std::string& s){
	const char* p = s.c_str();    
	userDefined(p);
	userDefined(s.c_str());                         
}

In this example, the user-defined function userDefined is overloaded to accept either a const char* or a const std::string parameter. Polyspace flags the use of a C-string instead of an std::string object to call the function.

Correction

To fix this issue, avoid using C-strings when you can use an std::string object instead.

#include <string>
#include <string>
#include <utility>
extern void userDefined(const char *);
extern void userDefined(const std::string &);
void bar( const std::string& s){                       
	userDefined(s);                     
}

Result Information

Group: Performance
Language: C++
Default: Off
Command-Line Syntax: EXPENSIVE_C_STR_STD_STRING_OPERATION
Impact: Medium
Introduced in R2020b