7 ms·
std::optional can be described as memory-unsafe because indirection when an optional is empty has undefined behaviour rather than deterministically throwing an
by hvdijk 4y ago
std::optional can be described as memory-unsafe because indirection when an optional is empty has undefined behaviour rather than deterministically throwing an exception or aborting the program, and may misbehave in all sorts of spectacular ways, including accessing random memory, on common implementations. Thankfully, implementations can and do provide ways to get it to behave more predictably.
- kllrnohj 4y agoBut that's not actually true in general. std::optional<int> has no such indirection issues, for example. If you're saying that blindly calling `operator->` on a std::optional<int*> without ever checking has_value() or similar can result in dereferencing garbage then yes, sure? But calling that "explicitly memory-unsafe" seems misleading at best, and just aggressively wrong at worst. You can always use `value()` if you want an error-throwing option, just like std::vector has at(). The standard didn't just ignore that.
- spoiler 4y agoI think the phrasing of "explicitly memory-unsafe" is wrong[1] in letter, but true in spirit. At the end of the day `std::optional<T>` is only marginally better than `T*`. And I'm sometimes not even sure if it's better or worse; the extra API surface you describe is nice, but in practice it's just a mirage of safety, since its API subset includes that of a common pointer. And I've seen ample code use the unsafe API because (convenience/performance/inexperience). But at the end of the day, I guess my comment is also irrelevant, because we as developers should strive for correctness, not brevity, in code. If we can achieve both, the better. But alas, brevity and correctness are in an antagonistic tension in C++. So when we want correctness in C++, we should also be prepared to swallow a large portion of spaghetti Bolognese. [1]: Or at least no more melodramatic than the phrase "aggressively wrong" lol
- kllrnohj 4y ago> At the end of the day `std::optional<T>` is only marginally better than `T*`. It's dramatically better than `T*` if your data isn't a pointer in the first place.
- hvdijk 4y agoSure, std::optional<int> is unlikely to result in such behaviour in practice, and std::optional<int*> is likely to "only" result in such behaviour if the result of operator*() is dereferenced again, despite both already being UB. Think of non-POD types, such as std::optional<std::string>, though: when you end up using uninitialised std::string objects, things do break in practice because of pointers used internally to implement std::string, and badly so. The fact that checked versions exist but are not used by default, have to be explicitly opted into, is consistent with C++'s designs and may be used to defend the current design, but at the same time also means it describing std::optional as memory-unsafe becomes a valid opinion based on facts, I think.
- kllrnohj 4y ago> but at the same time also means it describing std::optional as memory-unsafe becomes a valid opinion based on facts But your "facts" are "if I use the API wrong, it behaves wrong." But std::optional isn't easy to accidentally misuse here, unlike string_view (an actually "unsafe" addition). The argument that optional is broken if you both don't use has_value and don't use any of the other helpers (like value_or() or value() or transform or etc...) then it has UB means that optional is "broken by design" is not a very strong position to take. It's hard to imagine this being a problem in practice. It's pretty encoded in the code that it's optional, to just completely ignore that and blindly access it anyway seems pretty self-evident as a usage issue. Yes bugs happen, but come on. This is not a particularly sharp edge in C++'s toolbox here. It's a pretty straightforward, intuitive type, doing pretty much exactly what it says it does, exactly how you'd expect it to do. Should operator->() and value() be swapped? maybe, but then it'd be inconsistent with std::vector & other older types. And that inconsistency is probably worse overall.
- hvdijk 4y ago> But your "facts" are "if I use the API wrong, it behaves wrong." Kind of, yes. That is what memory safety is about, isn't it? If I look for definitions, I find for instance <https://hacks.mozilla.org/2019/01/fearless-security-memory-safety/ https://hacks.mozilla.org/2019/01/fearless-security-memory-s...>, explaining it as: > When we talk about building secure applications, we often focus on memory safety. Informally, this means that in all possible executions of a program, there is no access to invalid memory. Violations include: > - use after free > - null pointer dereference > - using uninitialized memory > - double free > - buffer overflow std::optional does not itself protect against using uninitialised memory, it merely provides the tools by which the programmer can prevent using uninitialised memory. Isn't that exactly what memory safety is about, about having std::optional somehow automatically ensure that that doesn't happen? If that isn't what memory safety is, what, in your opinion, does it mean instead? Note that I have attempted to refrain from posting my opinion on whether C++ made the right call or not. That is a separate question from whether it qualifies as memory-safe.