Expensive constant std::string construction

A constant string object is constructed form constant data resulting in inefficient code

Description

This defect is raised when both of these conditions are true:

  • You construct a std::string object from constant data such as a string literal or the output of a constexpr function.

  • The std::string object remains constant or unmodified after the construction.

This checker does not flag class member variables, and string literals that are function arguments.

Risk

Consider an std::string objects in a code block that contains constant data which remains unmodified after construction. Every time the code block executes, a new std::string object is constructed with no change in its content. Repeated construction of such an std::string object with no modification of the content is inefficient and difficult to detect. Consider this code:

#include <string>
constexpr char* getStrPtr() { 
	return "abcd"; 
}
void foo(){
	std::string s1 = "abcd";
	std::string s2{"abcd"};
	std::string s3 = getStrPtr();                 
}
int main(){
//...
for(int i = 0; i<10000; ++i)
      foo();
}
In this code, the function foo is called 10000 times. Each time foo is called, s1, s2, and s3 are constructed from the same constant string literal abcd, resulting in inefficient code. Because such inefficient and confusing code compiles and functions correctly, the inefficient construction of std::string objects from constant data might not be noticed.

Fix

The fix for this defect depends on the intended use of the constant data.

  • You can store the constant data in a static string object if you need the functionalities of std::string class.

  • You can use the constant data directly as temporary literals if you do not need to reuse the data.

  • You can store the constant data by using a const character array or an std::string_view object if you do not need the functionalities of the std::string class. std::string_view is supported by C++17 and later.

Consider this code:

constexpr char* getStrPtr() { 
	return "abcd"; 
}
void foo(){
	static  std::string s3 = getStrPtr();
	 std::string_view s3a{s3};                
}
int main(){
//...
for(int i = 0; i<10000; ++i)
      foo();
}
The std::string object s3 is declared as static. Because s3 is static, it is constructed only once even if foo is called 10000 times. The std::string_view object s2 shows the content of s3 and avoids constructing an std::string object every time foo is called. By using std::string_view and static objects, you avoid unnecessary construction of constant std::string objects. This method also clarifies that the objects s3 and s3a represent the same data.

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

Examples

expand all

#include <string>
constexpr char* getStrPtr() { 
	return "abcd"; 
}
constexpr size_t FOUR(){
	return 4; 
}
size_t getCount();
void CallFunc(std::string s);
void foo(){
	std::string s1 = "abcd";
	std::string s2{"abcd"};
	std::string s3 = getStrPtr();  
	std::string s4("abcd", FOUR());
	std::string s5("abcd"), s6("abcd");
}

void bar(){
	std::string s3a("abcd", getCount());
	char *p = "abcd";
	std::string s_p = p;
	CallFunc("message");
}

In this example, several const std::string objects are declared.

  • Polyspace® flags the std::string objects s1,s2, s5, and s6 because if these strings are constructed from constant data every time foo is called but remains unmodified after construction.

  • Polyspace flagss4 and s3 because they are constructed from compile-time constants, such as the constant literal abcd and the output of a constexpr function. The object s3a is not flagged because the output of getCount is not a compile-time constant.

  • Polyspace does not flag these objects when they are constructed from constant data:

    • An object that is not an std::string, such as *p.

    • Temporary objects that are constructed as a function argument, such as the object containing the string literal message in the argument of CallFunc.

Correction

You can fix this defect in several ways. For instance:

  • You can declare the std::string objects as static. When the object is static, the compiler does not reconstruct it in different scopes. When you need the functionalities of an std::string class, this declaration is a good fix.

  • You can store the constant data in a character array or character pointer. These objects are less expensive compared to an std::string.

  • You can declare the constant strings as std::string_view objects. These objects do not contain a copy of the constant strings, which makes these objects efficient.

#include <string>
#include <string_view>
constexpr char* getStrPtr() { 
	return "abcd"; 
}
constexpr size_t FOUR(){
	return 4; 
}

void foo(){
	static std::string s1 = "abcd";
	std::string_view s2{s1};
	const char *p = getStrPtr();
	std::string s3 = p;
	static std::string s4("abcd", FOUR());
	std::string_view s5{s1}, s6{s4};
}

The checks on s1 and s4 are fixed by declaring them as static. The checks on s2, s5 and s6 are fixed by declaring them as std::string_view objects. The check on s3 is fixed by storing the constant data in a character pointer.

Result Information

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