C++ brace initializer list, temporary lifetime

2019-09-06 03:46发布

问题:

I've got following code:

string join(initializer_list<string_view> strings);

initializer_list is std::initializer_list and string_view isn't std::string view but very similar class with constructors from const string& and const char*.

Then I've got following invocation of join:

EXPECT_EQ("this", join({ string("this") }));

After small investigation I've found that the first element of resulting initializer list isn't "this" but "\0his". This is becouse the destructor of the temporary created by string("this") is called right after creation of temporary string_view (so it contains invalid pointers). Why is it so that the lifetime of string("this") isn't extended to the end of the full-expression EXPECT_EQ("this", join({ string("this") }));?

Edit

Ok, as you suggested there is self-contained example:

#include <iostream>
#include <string>

using namespace std;

class string_view {
public:
    string_view(const string& str)
        : _begin(str.data())
        , _end(str.data() + str.size()) {
    std::cout << "string_view(...)" << std::endl;
    }

    const char* _begin;
    const char* _end;
};

void join(initializer_list<string_view> strings) {
    std::cout << "join(...)" << std::endl;
    for (auto i = 0u; i < 5; ++i) {
        std::cout << int(strings.begin()->_begin[i]) << " " << strings.begin()->_begin[i] << std::endl;
    }
}

int main() {
    join({ string("this") });
    return 0;
}

The output of this program compiled with last Visual Studio C++ (Express):

string_view(...)
join(...)
0
104 h
105 i
115 s
0

It may vary from compiler to compiler as above program is probably ill-formed.

I've investigated what is the order of calls in debugger, and there is the sequence:

main()
    basic_string(const char*)
    string_view(const string&)
    ~basic_string()
    initializer_list(...)
    join(...)

I would like the content of the string("this") to be available inside join function. And it is not the case, becouse `string("this") is destroyed before.

Why is the destructor of temporary string string("this") invoked before the join function is called or in other words why isn't the lifetime of string("this") extended to the end of the full-expression join({ string("this") })?

回答1:

What I think might be happening here is that you are constructing your initializer_list from a reference to your string, so that the string goes out of scope when your initializer_list has finished constructing.

You see your string is not a parameter to the join() function but to the initializer_list you construct.

My guess is that the initializer_list is constructed, your string goes out of scope and then your join() function tries to access the dead string through the reference contained in the string_view.