3

There seem some strange behaviors for pf.string() output, where pf is generated with p.filename(), where p is of type boost::filesystem::path and constructed with char const* or std::string.

Here is the code segment:

#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

int main(int argc, char **argv) {
  fs::path p(argv[0]);  // or fs::path p((std::string(argv[0])));
  fs::path &&pf = p.filename(); // or fs::path pf = p.filename();
  std::string const &name = p.filename().string();
  std::cout << "*" << name << "*\n";
  std::string const &p_name = pf.string();
  std::cout << "*" << p_name << "*\t";
  std::cout << "*" << name << "*\n";
  std::string s_name = p.filename().string();
  std::cout << "*" << s_name << "*\t";
  std::cout << "*" << name << "*\n";
  return 0;
}

The argv[0] here is fs.out and the output of the executable(compiled with clang3.4/ gcc4.9 with -O3/-O0) is:

**
*fs.out*    **
*fs.out*    *fs.out*

The boost version I used is 1.55 from Debian jessie(testing) package.

My questions:

  • Why name is empty in the first 2 lines?
  • Why p_name is not empty but name is empty at Line 2?
  • Why this program has the correct(?) output at Line 3 although it seems that there is no relationship between s_name and name?
Hongxu Chen
  • 5,240
  • 2
  • 45
  • 85

2 Answers2

3

You're taking references to temporaries.

Iff bound to a const reference (like p_name) the lifetime of the temporary will be extended to the end of the containing scope.

Otherwise you're simply invoking Undefined Behaviour. This also explains how name changes when you assign to a totally different variable. This apparently happens because s_name happens to allocate the same memory chunk that name still (erronously!) refers to. Much worse things might happen.

You should take the return values of filename() (and friends) by value (which should on modern compilers automatically behave as a move if the type supports that).

Note that MSVC does "appear" to accept this code and "do what you expect" - presumably because it has a non-standard extension that allows the lifetime of temporaries to be extended even when bound to non-const references.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Hmmmm... I am in doubt. Is `p_name` really valid because it's a const reference? Or does it "work" because he's working on `pf` instead of `p.filename()`? (I don't take references to temporaries, `const` or no, so I'd like to know about the fine print here.) – DevSolar Sep 11 '14 at 13:31
  • @DevSolar It does: http://stackoverflow.com/questions/2784262/does-a-const-reference-prolong-the-life-of-a-temporary (also: [GotW #88](http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/)) – sehe Sep 11 '14 at 13:33
  • Thanks, I realize my mistake. I was thinking that as long as the return value is reference I can assign it to `const&`; but obviously I was wrong since `p.filename()` is destroyed. – Hongxu Chen Sep 11 '14 at 13:37
  • BTW, should I always use `fs::path pf = p.filename();` instead of `fs::path &&pf = p.filename();` to rely compilers to do the optimization? – Hongxu Chen Sep 11 '14 at 13:39
  • Compilers should do the move assignment if available (return-by-value is always treatable as _rvalue ref_) – sehe Sep 11 '14 at 13:41
  • 1
    @HongxuChen: Depending on the rest of your code, leaving such optimizations to the compiler might have the advantage of least confusion. Quite some C++ coders out there haven't quite caught up with C++11 yet, and if the compiler "does" C++11, you get the optimization implicitly. (I am doing three-platform portable code 9-to-5, and two out of three compilers involved still balk at C++11 code... :-( ) – DevSolar Sep 11 '14 at 14:08
  • @DevSolar Thanks, I think can avoid `Object &&o = FunctionReturnByValue()` aggressively; I've seen a few talks saying that that's not good practice and rvalue reference had better be used in function argument passing(together with std::move) and when the performance is really important. – Hongxu Chen Sep 11 '14 at 14:23
  • @DevSolar @sehe i just realized that there are still some benefits to use `Object&&o = FunctionReturnByValue()`. Here without `&&` the returned value is used as the argument of `Object`'s copy constructor, when NO `move constructor` for `Object` is provided. – Hongxu Chen Sep 12 '14 at 05:46
  • 1
    @HongxuChen: Only if your compiler is too dumb for Return Value Optimization (RVO)... – DevSolar Sep 12 '14 at 06:41
  • @DevSolar You mean that `Object o` can still be constructed directly with the `return xxx` value inside `FunctionReturnByValue` for modern compilers and NOT a single `Object` is copied? I guess that may not be available if `Object` only has `Object(Object const&)` constructor? – Hongxu Chen Sep 12 '14 at 06:57
  • @HongxuChen technically, this is copy-initialization, and it's been around since c++03 (perhaps c++89): http://en.cppreference.com/w/cpp/language/copy_initialization. In particular, read under `2)`: _"The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used."_ – sehe Sep 12 '14 at 07:13
3

Nice one. ;-)

name is a reference. I.e., it only refers to p.filename().string(). That, however, is a temporary, i.e. it gets destroyed after the statement is done, leaving name to refer to invalid memory. You're in undefined behaviour country and are lucky your program didn't crash.

(sehe updated me on the fine print on const & with regards to your second question, so +1 to him.)

In the third round, s_name is an object, i.e. it holds a copy of p.filename().string() (and thus is valid). By some stroke of luck, the compiler apparently created that object in the same location that name refers to...

Needless to say, you should never rely on this kind of behaviour.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • 1
    +1 I'd assert that he's **unlucky** that the program doesn't crash. Then again, with c++, one should **never** expect to get lucky. – sehe Sep 11 '14 at 13:30