(original link: https://abseil.io/tips/5 translator: clangpp@gmail.com )
Weekly tip #5: the lost show
- Originally published on: June 26, 2012
- Updated on: June 1, 2020
- Short link: abseil.io/tips/5
"Don't know what you got till it's gone." --Cinderella
"Never regret until you lose" -- Cinderella band
Sometimes, in order to use the C + + library correctly, you need to understand both the library itself and the language. So... What is the problem in the following code?
// Don't do that std::string s1, s2; ... const char* p1 = (s1 + s2).c_str(); // No! const char* p2 = absl::StrCat(s1, s2).c_str(); // No!
Both s1+s2 and absl::StrCat(s1,s2) create temporary objects (here are string objects, but the same rule applies to any object). Member function c_str() returns a pointer to the underlying data, which is consistent with the lifetime of the temporary object. How long can temporary objects live? According to [class.temporary] in C++17 standard, "in the complete expression where the temporary object creation point is located, the destruction of temporary variables is the last step of the expression." (a "full expression" means "an expression that is not a subexpression of another expression.") In the above examples, when the expression on the right of the assignment operator ends, the temporary variable is destroyed, C_ The return value of str () becomes a dangling pointer. Formula (tl;dr?) (translator's note: tl;dr? Is an abbreviation, full name too long; don't read, often followed by a short summary statement): when you see a semicolon (usually earlier), the temporary object is dead. ah So how to avoid such problems?
Option 1: run out of temporary objects before the end of the full expression:
// Safe (although the chicken is a little weak) size_t len1 = strlen((s1 + s2).c_str()); size_t len2 = strlen(absl::StrCat(s1, s2).c_str());
Option 2: store temporary objects.
Now that you've created an object (on the stack), why don't you keep it for a while? It may be cheaper than it looks at first. Because of a gadget called "return value optimization" (and the mobile semantics of many value types, refer to( Tip #77 )), the temporary variable will be constructed directly on the assignment target object instead of copying:
// Safe (and more efficient than you think) std::string tmp_1 = s1 + s2; std::string tmp_2 = absl::StrCat(s1, s2); // tmp_1.c_str() and tmp_2.c_str() is secure.
Option 3: store a reference to a temporary variable.
C++17 standard [class.temporary]: "if a temporary variable is bound to a reference, or a child object of a temporary variable is bound to a reference, the lifetime of the temporary variable is extended to be consistent with the reference."
Because of the existence of return value optimization, this method is usually not cheaper than the storage object itself (option 2), and it may also give people a whole circle (Reference) Tip #101 ). (special cases requiring survival extension should be clearly noted!)
// Equivalent safety: const std::string& tmp_1 = s1 + s2; const std::string& tmp_2 = absl::StrCat(s1, s2); // tmp_1.c_str() and tmp_2.c_str() is secure. // The following behaviors hover on the edge of danger: // If the compiler can see that you are storing a reference to the inside of a temporary object, it will keep the whole object alive. // struct Person { string name; ... } // Generateperson() returns an object; GeneratePerson().name is obviously a child object: const std::string& person_name = GeneratePerson().name; // security // If the compiler doesn't see it, you're in danger. // class DiceSeries_DiceRoll { `const string&` nickname() ... } // GenerateDiceRoll() returns an object; The compiler doesn't see GenerateDiceRoll() Whether nickname () is a child object. // The following code may store a dangling reference: const std::string& nickname = GenerateDiceRoll().nickname(); // No!
Option 4: when designing a function, do not return the object???
Many functions follow this principle; But there are also many functions that do not comply. Sometimes it's really better to return an object than to ask the caller to pass in a pointer to the output parameter. Be careful where you create temporary objects. When manipulating a temporary object, anything that returns a pointer or reference inside the object may have a problem. c_str() is the most obvious culprit, but protobuf's (modifiable or other) accessors (getter s) and other common accessors can also go wrong.