Search code examples
c++pass-by-referencestdstringpass-by-valuepass-by-const-reference

Weird behavior with std::string reference class member


Given this code:

#include <iostream>

class Foo {
    public:
        Foo(const std::string& label) : label_(label) {}

        void print() {
            std::cout << label_;
        }

    private:
        const std::string& label_;
};

int main() {
    auto x = new Foo("Hello World");
    x->print();
}

I get

Hello World!

when I run it. If I modify it like this:

// g++ -o test test.cpp -std=c++17
#include <iostream>

class Base {
    public:
        Base(const std::string& label) : label_(label) {}

        void print() {
            std::cout << label_;
        }

    private:
        const std::string& label_;
};

class Derived : public Base {
    public:
        Derived(const std::string& label) : Base(label) {}
};

int main() {
    auto x = new Derived("Hello World");
    x->print();
}

I still get:

Hello World

but if I modify it like this:

// g++ -o test test.cpp -std=c++17
#include <iostream>

class Base {
    public:
        Base(const std::string& label) : label_(label) {}

        void print() {
            std::cout << label_;
        }

    private:
        const std::string& label_;
};

class Derived : public Base {
    public:
        Derived() : Base("Hello World") {}
};

int main() {
    auto x = new Derived();
    x->print();
}

I do not get any output. Can anyone explain this to me? I am compiling the program like this:

g++ -o test test.cpp -std=c++17

This is on Mac if it makes a difference.


Solution

  • All three pieces of code are incorrect, label_ is merely a pointer to a temporary std::string object "Hello World", being a temporary you can't guarantee that the string is still at the location pointed by label_ at the time of x->print().

    The compiler will issue dangling reference warnings if we use optimization, curious that only then it becomes aware of the problem.

    Using compiler flags -Wall -Wextra -O3 with gcc 13.2:

    https://godbolt.org/z/9xjsxhrTT

    Speculating, perhaps the fact that the temporary is in main, where the object is declared, and thus within scope, despite being an argument, allows it to live long enough. In the third case the temporary is passed directly to the base constructor, and therefore it may get discarded before x->print(). main, where the action takes place, has no knowledge of the temporary.

    Coming from Java or C#, where everything but primitive types are passed by reference with no worries, this may cause some confusion, the fact is that with C++ this is not the case, it is incumbent upon the programmer to choose, a reference class member will not hold outside referenced data, if it's temporary it will go away as soon as the program sees fit in its memory management. In this case, as stated in the comment section, you should pass the data by value, not by reference, the owner of label_ is Foo, it is where it's supposed to be stored.