2

I am learning books <Effective C++>, i think use const reference is a good practice. because it can avoid unnecessary copy.

so, even in initialize a object, i use const T & t = T();

here is the code:

#include <string>
#include <iostream>
#include <vector>

using namespace std;

template <class T>
inline std::vector<std::string> Split(const std::string &str, const T &delim, const bool trim_empty = false) {
  if (str.empty()) return {}; 
  size_t pos, last_pos = 0, len;
  std::vector<std::string> tokens;
  std::string delim_s = ""; 
  delim_s += delim;
  while(true) {
    pos = str.find(delim_s, last_pos);
    if (pos == std::string::npos) pos = str.size();
    len = pos-last_pos;
    if ( !trim_empty || len != 0) tokens.push_back(str.substr(last_pos, len));
    if (pos == str.size()) break; 
    else last_pos = pos + delim_s.size();
  }   
  return tokens;
}

int main() {
  const std::string& str = "myname@is@haha@";  // compare with std::string = "", will this be better(in speed and memory)?
  const std::string& s = Split(str, "@").front();  // this crashed
  // std::string s = Split(str, "@").front();  // this ok
  cout << "result is " << s << endl;
}

As you see above, this code is used to split a string into vector<std::string>,

in main function:

I have two method to get the first element of the splited results.

1.const std::string& s = Split(str, "@").front();

2.std::string s = Split(str, "@").front();

in my test, option 1 will crashed, option2 is ok.

could you talk some difference of these?

and is it necessary to do this (const std::string& s = "asd";)?

comparing to std::string s = "asd", what is the advantages and disadvantages of them?

nick
  • 832
  • 3
  • 12
  • "so, even in initialize a object, i use const T & t = T();" It actually doesn't buy you anything at best, and in case of `const std::string& s = Split(str, "@").front()` is simply wrong. So don't do that. – n. m. could be an AI Sep 01 '22 at 04:25
  • That is a very old book at this point. Are you're stuck on C++11 or did you just tag it because that is what the book references. If you aren't stuck on C++ 11, i'd come back to that book until you've learned the better ways to handle things that C++ has introduced in the last 11 years. – Taekahn Sep 01 '22 at 04:27
  • @n.1.8e9-where's-my-sharem. so const T &t = T() is equal with T t = T()? – nick Sep 01 '22 at 04:58
  • `const T t(something)` or `const T t = T(something)`. They all compile to same exact code, [see](https://godbolt.org/z/vsaTenofE). – n. m. could be an AI Sep 01 '22 at 05:08
  • I never advocate using a particular approach "as much as possible". I advocate using techniques that are suited for the job at hand and are easier to understand, so help make it easier to get the code doing what is intended while avoiding errors. Slavish rules of "use XXX as much as possible" encourage the programmer to do things without understanding how they work, or what the benefits or adverse consequences are. Some of the worst code I have seen, professionally, has been due to developers following rules slavishly. – Peter Sep 01 '22 at 05:18
  • @nick, refer to the answer by Silvio Mayolo. When you call `front` on a `vector`, a reference (and not a copy) to the front element is returned. The `Split(str, "@").front()`, which is on the RHS, is refering to an element in a temporary vector. This vector is going to destroyed at the end of this statement. Thus, the strng that this reference points to has to be copied to a string, it cannot be held in a reference to a string. – Hari Sep 01 '22 at 05:20
  • *...as much as possible?* I'd use it **as much as appropriate**. I wouldn't make my code go through inappropriate contortions to conform to using that pattern. It is a good pattern, so you'll probably be using it frequently. Especially for parameters. – Eljay Sep 01 '22 at 11:40

2 Answers2

3

Someone has to own data. C++, like many low-level languages, fancies itself as a single-ownership language in general (there are things like std::shared_ptr that behave differently, but the default for scalar values is single-ownership). That means that someone, somewhere, has to actually store that data. That might be a structure or class, that might be a local variable in a function, or it might be the static memory of your program itself. Once someone owns the data, then, and only then, can you take references to it.

const std::string& s = Split(str, "@").front();

Split(str, "@") returns a std::vector. By value, which means the caller of Split is taking ownership of that value. Now, that std::vector is a temporary value and, unless we do something to change it, it will disappear at the end of the current expression (basically, when we hit the semicolon). So we call front(), which returns a reference to data in the vector. Then we destroy the vector. It's gone, forgotten about, never existed. But we have a reference to it, so that reference is now garbage. The problem is that no one owns the vector anymore, so it's gone. On the other hand,

std::string s = Split(str, "@").front();

Here, we copy the std::string out of the std::vector before destroying it. The new string has no actual ties to the vector, it merely looks just like a value in the vector, so when the temporary std::vector is destroyed, it doesn't affect us.

If you're really intent on avoiding copies, you can take ownership of the vector as a local variable. I think this is overkill for std::string, but if your data structure was incredibly difficult (or even impossible) to copy, this might be warranted.

std::vector<std::string> my_vec = Split(str, "@");
const std::string& s = my_vec.front();
// s is good until my_vec goes out of scope, then it's garbage.
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • thanks Silvio, but i am still confused why `const std::vector& r = Split("asd", 'a');` works, in my understanding, the right part construct a temp variable, and it will be released after `Split` finished, and r will be garbage, but it seems work and r can be used correctly? – nick Sep 01 '22 at 05:15
  • @nick, Could you show us code where `const std::vector& r = Split("asd", 'a');` works? – Hari Sep 01 '22 at 05:23
  • 1
    @Hari `const std::vector& r = Split("asd", 'a'); for (const auto & i : r) cout << i << endl` – nick Sep 01 '22 at 05:25
  • @nick refer to this answer: https://stackoverflow.com/a/2784304/1047213. "The lifetime extension is not transitive..." – Hari Sep 01 '22 at 05:53
  • 1
    This answer ignores lifetime extension... "The fact that that works at all (and it does work on my machine, at least in a trivial example) is happenstance" is wrong. – Jeff Garrett Sep 01 '22 at 13:14
  • 2
    @JeffGarrett I was not aware lifetime extension was even a thing. So I'm glad Miles' answer is the accepted one. I'll remove the offending paragraph so that this answer is correct (if incomplete). – Silvio Mayolo Sep 01 '22 at 13:25
3
const std::string& str = "myname@is@haha@";

This works due to reference lifetime extension. When a prvalue is bound immediately to a reference-to-const or an rvalue reference its lifetime gets extended to the lifetime of the reference. Since "myname@is@haha@" is not a std::string, a temporary std::string gets constructed to hold that value. That temporary is a prvalue, and so is eligible for lifetime extension.

const std::string& s = Split(str, "@").front();

This doesn't work because std::vector::front doesn't return a prvalue, it returns an lvalue. Reference lifetime extension does not apply in this case. The object referenced by the reference returned by front is part of the std::vector returned by Split, and dies along with it at the end of the expression leaving s dangling.


This part is wandering a bit into opinion, but in general if you want an object, just declare that you have an object. There are far fewer pitfalls, and given modern compiler optimizations there is no performance benefit to relying on reference lifetime extension. Anywhere you may have saved a copy by relying on reference lifetime extension, copy elision (guaranteed in C++17 and above, widely applied by compilers prior to that) will save the copy for you.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52