20

Assume we have a simple getter method in a class that returns a const reference to a std::string member:

const std::string& getString() const noexcept { return someString; }

With the advent of std::string_view in C++17, I wonder whether it has any advantages of writing this instead:

const std::string_view getString() const noexcept { return someString; }

Does one method have advantages/disadvantages over the other? Clearly (correct me if I'm wrong) both solutions will definitely be better than this:

const char* getString() const noexcept { return someString.c_str(); }

I've seen this related question, but I'm asking for something slightly different.

1 Answer 1

25

Yes, you should write:

const std::string& getString() const noexcept { return someString; }

Instead of (note: not const, because never return const values):

std::string_view getString() const noexcept { return someString; }

The reason is - you already have a string. So it's not like you have to pay anything extra to get a string out of it. And string has one notable semantic difference to an arbitrary string_view: it's null-terminated by guarantee. We know this. Maybe some downstream user needs to rely on that information. If they need null-termination (e.g. they need to pass to some C API that requires it) and you give a string_view, they have to make a string out of it themselves. You save nothing, but potentially make downstream users do more work.

If, however, you had a vector<char> instead... then I would suggest to return a span<char const> or the equivalent thereof. Since there is no semantic difference and you're just providing a view.


There also the separate argument of what:

auto x = obj.getString();

should do. This either takes a copy of the string (expensive, but safe) or effectively a reference to it (cheap, but potentially dangling). But it doesn't entirely look like a reference, it looks like a value. This is a broad issue with reference-semantic types in general (things like reference_wrapper, string_view, span, tuple<T&...>, optional<T&> if it existed, etc.).

I don't have an answer for this case, but it's something to be aware of.

Sign up to request clarification or add additional context in comments.

9 Comments

Regarding the second part: I think in the const std::string& case lifetime extension would kick in, which is probably something you loose when using string_views... would this probably be another argument for preferring a const reference instead of a string view?
I've seen way too many bugs caused by assuming std::string is null terminated. Unless using c_str() I'd recommend never assuming. Sure, it happens to be guaranteed but when changes are made to the code you want the compiler to catch errors, not happily give out pointers to std::vector<char> where someone assumed *v.end() is a legal pointer to a null terminator.
@andreee lifetime extension will only kick in if your getter returns a prvalue std::string getString(). You'll have a dangling reference doing auto& str = make_obj().getString()
@ZanLynx What do you mean? std::string guarantees null-termination on basically every kind of access since C++11...
@FabioFracassi So the rvalue overload returns a string and the lvalue one returns a string_view? Dunno how I feel about that. (Also, hi Fabio!)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.