8 ms·
It's not that people can't deal with pointers. The most common issue in C++ is how many ways to shoot yourself in the foot with common utils. Like, we use a lot
by hot_gril 4y ago
It's not that people can't deal with pointers. The most common issue in C++ is how many ways to shoot yourself in the foot with common utils. Like, we use a lot of abseil utils at work that return string_views instead of new strings, and if you're directly passing that somewhere else, you easily corrupt your memory.
std::string foo = "foo";
const std::string foo2 = absl::StripAsciiWhitespace(absl::AsciiStrToUpper(foo)); // in Python: foo2 = foo.upper().strip()
std::cout << foo2; // garbage
Yeah, all technically the user's fault, but it gets pretty tiring worrying about this stuff when you're writing a high-level application and the few CPU cycles you save don't really matter. No matter how good you are, you will make mistakes, and the chance of one bug corrupting memory in your whole process increases exponentially with the LoC written. Also I have to point out the irony in a language being type-safe but not memory-safe.
Also, I didn't expect Java-looking class syntax to leave member variables uninitialized (garbage) if you don't set a default. That should at least be a compiler warning, I mean how often do you actually want that?
- okamiueru 4y ago> Also I have to point out the irony in a language being type-safe but not memory-safe. Misnomer aside, there is no contradiction. Type safety is much easier to reason about at compile time than memory management. So, it shouldn't come as a surprise. You need a rust like "borrow checker"-by-default functionality.
- hot_gril 4y agoIt's not surprising that they use static types in C++, but it's not about safety. It's about knowing the size of everything at compile time. Dynamic typing requires moving more towards the "interpreted" side of languages and incurring the performance cost of that. The contradiction is when the veteran SWEs I work with say we use C++ instead of Python for the added safety of compile-time type checks, treating dynamic typing like a bigger risk than memory trampling (never mind that Python has type-checking too if you really want it).
- bluGill 4y agoFor very large projects static types in C++ are a lot safer than Python. For simple projects I agree with you, but as you get over 50k loc Python becomes hard to manage, while i'm work in 10 million lines of C++. Sure it takes more effort, but at that size C++ is a lot easier to manage. C++ does have issues. However few languages can handle complex problems well. (Rust is very intriguing for the possible ability to do things at the same scale)
- hot_gril 4y ago10 million C++ lines is 10 million chances for you to misuse memory and cause your entire program to behave unpredictably, from simply crashing to writing garbage directly into your database. Imagine you're on a team of 8 and find one day that your DB's indexes were mysteriously corrupted, what do you do next? It's happened here. If you use the wrong type in Python, you get an unhandled exception failing to find a property or something, that's about it. Maybe there's a contrived scenario where it'll succeed in a wrong way, but again that's a smaller blast radius
- bluGill 4y agoThe likelyhood of Python crashing is a lot higher as it is much harder to know you didn't break your error handling path someplace that isn't tested. C++ sometimes has memory issues, but not nearly as often as Python has problems in the error paths.
- hot_gril 4y agoPython is exception-based, so it's easy to avoid a crash. Say it's a webserver, you catch all exceptions around each endpoint and give HTTP 500 if it's not something you understand to be a 4xx. Exceptions are nice in that you either explicitly handle them or the caller will. C++ has exceptions too, but I've never used them, so idk. The abseil statuses end up being used like DIY exception handling, and it's ok but a bit easier to mishandle something than in Python. And a segfault cannot(? or shouldn't) be caught and will crash the whole program.
- titzer 4y ago> Yeah, all technically the user's fault, The problem is C++'s priorities. Priority #1 is performance, and if you look closely, nearly every single instance of this buggy-program hostile attitude comes down to the language and libraries absolutely refusing to do any kind of dynamic safety checking by default. UB can be traced directly from "it's too hard to reason about the implications of buggy programs" to "well, don't do that, stupid programmer".
- deleted 4y ago[deleted]
- vlovich123 4y agoThere's a talk by Scott Meyers (at the D lang conference IIRC), where he methodologically takes apart the claim that priority #1 is performance or 0-cost abstractions. Basically the way the standards body seems to work is there's ~N different concerns and people will whatever set is appropriate to kill proposals. It gives a fantastic illusion of performance being #1, but C++ makes plenty of choices that are misaligned with performance (e.g. variable aliasing that it inherited from C but it let infect the C++ type system too).
- hot_gril 4y agoThere are tradeoffs. If performance were always #1 with the underlying requirement of portability, you'd be using C. And performance is more of a concern in C++ than in Java. I'm no expert in this, I'm just a guy who's sick of writing web backends in C++ for no reason.
- pjmlp 4y agoAh the days of ISAPI ATL extensions. :)
- vlovich123 4y agoYou'd think so, but C doesn't have the performance crown always either due to language design choices. A big one is aliasing (which languages like Rust and Fortran forbid) which inhibits the ability for very impactful and common low-level optimizations that come up all the time. Performance characteristic differences between C vs C++ are not all that interesting because the language models are so similar (& thus similarly the compilers for them basically have the same model). Indeed, higher level of abstractions can help improve performance as virtual classes will outperform manual attempts at doing similar things (due to devirtualization passes within the compiler). That's actually been a friction point for Rust using LLVM because LLVM is built around the C/C++ language model and it's hard to express certain invariants to it that would result in even more efficient code or you try to use optimization passes that turn out to be broken because they're not really used by the broader C/C++ community (https://github.com/rust-lang/rust/issues/54878 https://github.com/rust-lang/rust/issues/54878).
- codemac 4y ago<source>: In function 'int main()': <source>:6:54: error: conversion from 'absl::string_view' {aka 'std::basic_string_view<char>'} to non-scalar type 'const std::string' {aka 'const std::__cxx11::basic_string<char>'} requested 6 | const std::string foo2 = absl::StripAsciiWhitespace(absl::AsciiStrToUpper(foo)); // in Python: foo2 = foo.upper().strip() | ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~ ASM generation compiler returned: 1 This is gcc with c++20, curious what the garbage is you're describing.
- hot_gril 4y agoOh sorry, foo2 should be auto in this example (which becomes string_view). StripAsciiWhitespace takes a string_view here: https://github.com/abseil/abseil-cpp/blob/master/absl/strings/ascii.h#L225 https://github.com/abseil/abseil-cpp/blob/master/absl/string... The garbage comes from AsciiStrToUpper returning a new std::string which StripAsciiWhitespace takes as a string_view (implicit conversion). By the time you print foo2, the string is already freed.
- opportune 4y agoThis is a classic misuse of rvalues so I’m not sure it’s a huge problem. I don’t think it’s a problem with C++. I probably work at the same place you do btw and I am pretty sure there are rules/guides about how to handle string_view lifetimes For those downvoting, please explain why you think string_view should take ownership of an rvalue string
- hot_gril 4y agoWhen you're writing a ton of web backend code dealing with strings all over the place, it's easy to forget to check whether something is accepting and returning a string or string_view. `auto foo2 = StripAsciiWhitespace(AsciiStrToUpper(foo))` doesn't say what's going on. I didn't downvote, btw. I don't do that in general.
- opportune 4y ago
- Camillo 4y agoNot to detract from your general point, but are you sure your example corrupts memory? AsciiStrToUpper returns a std::string (it has to), which in your example becomes a temporary object. Temporaries are destructed at the end of the containing full-expression, which in this case is the whole assignment expression. So StripAsciiWhitespace returns a view into a still-living temporary, and foo2's constructor allocates memory and makes a copy. Only then is the temporary deallocated. Now, if you wrote absl::string_view foo2 = etc., you'd have a dangling view for sure. In practical industrial usage, you'd build with an address sanitizer (it's built into clang: -fsanitize=address), which should catch that issue.
- hot_gril 4y agoI missed one important little piece in my example, should be `auto foo2` (to make it more of a landmine) or `string_view foo2` and not `string foo2`, as you've noticed. There's an overloaded func taking string_view and returning string_view. We use an address sanitizer in tests but not in production. IIRC we had one untested log output line like this that caused corruption in prod.