Quant developer HFT course · Module 2: Safe C++
Dangling views: string_view and span
The question
A colleague replaced const std::string& parameters with std::string_view across the feed parser and one test started failing intermittently. What would you look for?
Why views exist
A std::string_view is a pointer and a length. A std::span<T> is a pointer and a count. Neither owns anything. They let a function read a sequence of characters or elements regardless of where it lives (a std::string, a literal, a buffer received from the network) without copying it into a new owner.
For a feed parser this is exactly what you want. A message arrives in a receive buffer; the parser needs to look at the symbol field, compare it, maybe pass it on. With std::string every field would be copied and possibly allocated. With a view, the field is two machine words pointing into the buffer that already holds it.
std::string_view field(std::span<const char> msg, std::size_t off, std::size_t len) {
return std::string_view(msg.data() + off, len); // no copy, no allocation
}Why they dangle
Because a view owns nothing, it is valid only while the thing it points into is alive and unchanged. Every bug with views is a case of the referent going away first.
std::string_view venue_of(const Order& o) {
return o.venue_name(); // venue_name() returns std::string by value
} // the temporary string is destroyed here
The function compiles because std::string converts implicitly to std::string_view. The view points into the temporary's buffer, which is freed at the end of the return statement. For short strings stored in the small buffer, the bytes may still be on the stack and the test may pass; for longer ones they are in freed heap memory. That matches the intermittent failure in the question.
The same thing happens when you store a view:
struct Subscription {
std::string_view symbol; // views a string owned by someone else
};
Subscription make_sub(const std::string& s) { return {s}; }
auto sub = make_sub(cfg.get("symbol")); // get() returns a temporary string: dangling at once
And when the owner changes: a view into a std::string or std::vector that later grows points at the old buffer.
Rules that keep views safe
- Use views as function parameters, not return values or members, unless the function returns a view into one of its own parameters and the lifetime is obvious.
- Never create a view of a temporary that outlives the statement.
- Do not store a view in a long-lived object unless that object provably lives shorter than the referent: a parser state that lives within one message's handling is fine; a subscription table that lives all day is not.
- Remember that the referent must not change. A view into a buffer that is reused for the next packet is valid only until the next receive.
- A
string_viewis not null-terminated. Passingsv.data()to a C function expecting a C string reads past the end.
When something must outlive the source, copy it into an owner. For short fields like symbols, a fixed-size char[8] or a small inline string avoids the heap.
In the failing test
The checklist for the colleague's change: any function that now returns a string_view, any struct member that became a string_view, any call site that passes a temporary string into a function that stores the view, and any view into a buffer that is reused. AddressSanitizer will name the line that freed the memory, which usually makes the fix obvious.
Check yourself
Question 1 of 3
What does std::string_view store?
It is two words and owns nothing. That is the source of both its speed and its dangers.
Question 2 of 3
Which of these is safe?
In the first, the temporary lives until the end of the full expression, which includes the whole call. The others keep the view beyond the statement or beyond the parameter's life.
Question 3 of 3
A parser keeps string_views into the socket receive buffer and processes them after the next recv into the same buffer. What happens?
The memory is still valid, so nothing crashes. It just holds different data, which makes this bug harder to notice than a use-after-free.
In the interview
Say your own answer out loud first, then open the one we would give.
When would you use string_view over const std::string&?
As a parameter type for functions that only read characters, because it accepts strings, literals and slices of buffers without constructing a std::string. I avoid it as a return type or a member unless the lifetime of what it refers to is obvious, because it owns nothing.
How can a string_view dangle?
By pointing into something that is destroyed or changed: a temporary string, a string that is reassigned or grows, a receive buffer that is reused for the next packet. Returning a view of a temporary is the classic case; it compiles because strings convert to views implicitly.
What is std::span?
The same idea for contiguous elements: a pointer and a count, viewing an array, a vector or a buffer without owning it. It is a good parameter type for functions that process a sequence, with the same lifetime rules as string_view.