Reference parameters are awful; the reader has no idea whether the argument will be modified or not without reading the header file.
With const pointers you never have this problem.
Reference parameters are awful; the reader has no idea whether the argument will be modified or not without reading the header file.
With const pointers you never have this problem.
Also the “problem” you describe is not solved by your proposed “solution”. A const pointer can point to something that can be modified.
You can declare a pointer to a const object, or, you know, declare a const ref.
_Success_(return) //out parameters should be set if this returns true
bool tryGetValue(
_In_ Map const* map, //pointer is for input only and cannot be null
_In_ char const* name,
_Out_opt_ Data* value //pointer is for output and can be null
);
If you pass null for _In_ params, the compiler will warn or error. You return true without setting the _out_opt_ parameters, the compiler will warn or error. You can pass null to _out_opt_, and the compiler will warn or error if the function body doesn't properly check if the parameter is null(Although this is something I'd 100% want C++ to do, and not with an annotation but with a very short operator or reserved word.)
> A pointer can be nullptr while a reference cannot so that would be a step backwards.
Backwards from what? A reference can't be null, but it can still be invalid, which is a more common problem than forgetting to check for null-ness.
> You can declare a pointer to a const object,
Yes. I should have said that instead.
> , or, you know, declare a const ref.
That doesn't solve the original problem with references, though. I hate reading `foo (bar, baz)` and having to dig into the header to find out that bar or baz or both might be modified after the call to foo.
When reading code, I want to be able to skip over anything that does not modify the state of variables in the current scope.
Using references means that I have to know what every single call does. Using pointers exclusively, I can simply skip calls to functions with arguments that aren't pointers.
no, a reference cannot be invalid. If you are given an invalid reference (such as referring to an object after its lifetime ended, or referring to something that is not an object of the reference's type), then the fault lies on the caller. The callee cannot check the reference's validity, nor it is its responsibility to do so.
This is *very* different from the case of a pointer, where the caller is not at fault, typesystem wise, for giving a nullptr to a function accepting a pointer.
Increasing the probability of runtime errors to gain a little bit of readability at the caller's site is not a good tradeoff.
With const references you never have this problem.
Const references are less offensive, but I'm not going to pass non-const by pointer and const by reference, that's just inconsistent for no reason.
If null is not a valid value, throw an assert at the beginning of the function. It's not like references can't be in a broken state, they're just syntax sugar for pointers (that also happens to wildly complicate the language's type system)
Why use pointers at all? Just use const references for immutable stuff, regular references when you want mutation, normal values when you want copy semantics, and smart pointers for other cases (unique_ptr for move semantics, shared_ptr for lack of clear ownership, etc.) If you want to represent nullability, std::optional is a good choice.
> If null is not a valid value, throw an assert at the beginning of the function
Yikes. This means the compiler will not catch it for you. Why make this a runtime issue? Use the type system to your advantage.
As for smart pointers there's a lot of domains with tight performance requirements where you can't afford them - and to be quite frank, if you can afford to use shared_ptr you can also afford GC, so just use a sane language
How? Honestly curious. (You can certainly reinterpret_cast something and ignore all warnings, but is that all?)
I find this all a bit rich anyway. In languages like C#, everything is a bloody reference and this lame concept of "reference types" corrupts developer minds.
It is true that C# has value types that are by default passed by copy, though.
When I'm navigating code, the references aren't a problem, because I am using an IDE of some sort, usually.
The complaint I made was about reading, not navigating, for example when reviewing a PR.
Class types are effectively pointers that are passed by the value of the pointer.
You cannot implement swap in Java without eg wrapping the params in one element arrays.
So? It still helps me with decoding `foo(bar, baz)`, as without the `&` I KNOW that bar and baz would not change after the call. And since both bar and baz are in the local scope, I already know whether they are pointers or not.
With pointers, all the information necessary to determine if bar and baz would change after a call to foo is in the local scope. With references that information is elsewhere.
While I would certainly prefer the reference to be visible at the call site (a mistake Rust did not repeat), if I have to choose, I prefer the inconvenience of having to use an IDE like qtcreator that will display references in a different style, to introducing an invalid value that needs to be handled (and will be mishandled).
So yeah, as a senior dev, it checks out.
Hardly any different from "var" parameters in plenty of languages since the 1960's.
I found that to be a rare circumstance in any complicated code base. And once you lose that capacity, oh boy does this language become terrible fast.
Visual Studio works fine for millions of lines but I am sure the difference in experience can vary wildly.
In general, the company moves forward because most of the same people who established the code base are still working here and they just already know all the quirks and can Intuit argument types, often because they wrote all the classes in question.
No, we don't spend money on code editors around here; I meant VSCode.
Not spending money in tools sounds like a very strange economics for software development. If interviewing does not scare you I warmly suggest finding a new employer.
Nevermind that I have some experience with how Google did it, and it turns out there's a lot of ills you can solve with a 25,000+-strong engineering force that you can't in a smaller org. VSCode at Google never worked for me either, but punching a novel symbol into company-wide syntax-intelligent internal code search and glancing at the first response, which was likely to be the right one because it was a Google search engine so had well-tuned signals for "things humans care about," always did.
They're not interested in how Google does it, merely that they do it so you can't get fired for making an architectural decision that worked for Google. Nor are they really interested in solving the tooling issue because it works for them; they just grep-and-pray. They're very fast at it because it's all they know so they've gotten quite good at it (when they have to do it at all; often they just know the types because they wrote the classes. ;) ).
Besides the issue of C++ being just hard to compile (global symbol mutation via the macro system, the way templates instantiate, the compilation unit as the fundamental element of compilation meaning any individual compilation unit can require hundreds to thousands of include files as input), the language is rife with undefined behavior and assistive tooling is allowed to crash on undefined behavior. So, yes, if your tooling crashes it may indicate a problem with your code---but how are you going to find it now that your tooling has crashed?
We've had a couple of initiatives at my organization to try and get tooling operational across the code base. They haven't stuck. Developers keep having to fall back to grep and pray.
I prefer working with languages where there are simply fewer features and less undefined behavior. It makes the tooling more reliable.
This organization has its work to do. My advice to other teams starting out is "C++ may be cheap in terms of hiring people and libraries that exist, but consider the cost once your codebase gets large. This is not a language that lends itself to safe behavior or tool-comprehensibility, and if that's something your project needs, be prepared for that. Other languages are safer, other languages are simpler for tooling to digest."
So yeah, difficult-to-parse codebases are not an exclusive feature of C++.
I'm medium-level confident there's meat on the bones of optimizing it to a point better than the C++ tools because they made choices in the language that do some amount of compartmentalization, so you don't get that "one #define up in this header you've never seen changes the meaning of symbols in every other file in this compilation unit." Unless that is possible and I simply haven't gotten deep enough into Rust to realize it yet.
Back in the day, I once worked with a C++ game engine where the developer was obsessed with interfaces vs. implementation. Every class, even if there was one implementation, had an abstract interface wrapper. It was the first codebase I ever encountered where the compiler (on my Mac laptop) choked on it because it ran out of RAM trying to compile it.
It sounds like the problem is not using an IDE.
Use references whenever posssible. Then you don't need to do a null pointer check. C++ is a footgun. And like with all guns, security first. With guns - put the safety on. With C++ - check the pointers are not null.
OFC one can argue that with proper instrumentation invalid pointers are captured sooner than later, but the fact is it's much easier to represent invalid program state using pointer than references.
#define $
And add the $ in front of all non-const pass by reference parameters in the callThere is no perfect solution. Avoid out parameters if possible, otherwise make the semantics clear via function naming or at least parameter naming conventions.