8 ms·
If you're new to C++11, Captain Proto (particularly the kj library embedded within it-- src/kj ) is a great read: https://github.com/capnproto/capnproto https:/
by choppaface 6y ago
If you're new to C++11, Captain Proto (particularly the kj library embedded within it-- src/kj ) is a great read: https://github.com/capnproto/capnproto https://github.com/capnproto/capnproto kj semi-re-implements several core C++11 features like Own and Maybe (actually Maybe / std::optional is still pretty new!) https://github.com/capnproto/capnproto/blob/master/c%2B%2B/src/kj/memory.h https://github.com/capnproto/capnproto/blob/master/c%2B%2B/s... Why did Kenton do this? He can speak for himself, but the core of Captain Proto is /roughly/ like a serializable / portable memory arena, so it was necessary for the design. Reading through kj and _comparing_ it with C++11 will give you some great initial insight into why both are implemented the ways they are. I'm not really advocating you use kj directly or adopt things like capnp's unique comment style, but the codebase is nevertheless very well organized and clear.
Some of the older glog code is pretty nice with regards to a very vanilla and portable treatment of macros https://github.com/google/glog/tree/master/src/glog https://github.com/google/glog/tree/master/src/glog
While I wouldn't necessarily recommend Boost as a model project / repo ( https://github.com/boostorg https://github.com/boostorg ), it's worth checking out to help understand why modern decisions were made the way they were.
- kentonv 6y agoOh hello. Thanks for the kind words. > Why did Kenton do this? He can speak for himself, An incomplete list of reasons: 1) At the time I started the project, a lot of things that KJ replaces, like std::optional, didn't actually exist in the C++ standard yet. 2) A lot of the stuff in the standard library is just badly designed. Take std::optional, for instance. You'd think that the whole point of using std::optional instead of a pointer would be to force you to check for null. Unfortunately, std::optional implements operator* and operator-> which are UB if the optional is null -- that's even worse than the situation with pointers, where at least a null pointer dereference will reliably crash. KJ's Maybe is designed to force you to use the KJ_IF_MAYBE() macro to unwrap it, which forces you to think about the null case. 3) A lot of older stuff in the C++ standard library hasn't aged well with the introduction of C++11, or was just awful in the first place (iostream). C++11 really changed the language, and KJ was designed entirely with those changes in mind. 4) The KJ style guide (https://github.com/capnproto/capnproto/blob/master/style-guide.md https://github.com/capnproto/capnproto/blob/master/style-gui...) adopts some particular rules around the use of const to help enforce thread safety, the specific philosophy around exceptions, and some other things, which differ from the C++ standard library's design philosophies. KJ's rules have worked out pretty well in my experience, but they break down when building on an underlying toolkit that doesn't follow them. 5) This is a silly matter of taste, but I just can't stand the fact that type names are indistinguishable from variable names in C++ standard style. 6) Because it was fun. Do these reasons add up to a good argument for reinventing the wheel? I dunno. I think it has worked well for me but smart people can certainly disagree.
- typon 6y agoThis style guide basically forces you to write Rust in C++
- kentonv 6y agoI agree with this summary. Interestingly, though, at the time I wrote the guide, Rust was in its infancy, and I didn't know anything about it. :)
- typon 6y agoTbh, I actually prefer the C++ with your style guide than Rust. Now if C++ had a package manager and a crates.io equivalent, I wouldn't look back at Rust at all. Unfortunately, C++ is just too far behind. (Btw, I gave a presentation about your style guide at my company two years ago, trying to convince people that we should be doing this stuff ;)
- nyanpasu64 6y agoThere are some differences in the details between KJ C++, and both Rust and my Rust-inspired C++ guidelines: > Value types always have move constructors (and sometimes copy constructors). Resource types are not movable; if ownership transfer is needed, the resource must be allocated on the heap. In Rust, all types (including resources) are movable. > Value types almost always have implicit destructors. Resource types may have an explicit destructor. What's an explicit destructor? Rust's File type closes upon destruction, and one criticism of the design is that it ignores all errors. The only way to know what errors occurred is to call sync_all() beforehand. However, "Ownership" and "Reference Counting" (and "Exceptions" to an extent) feel very Rust-like. > If a class's copy constructor would require memory allocation, consider providing a clone() method instead and deleting the copy constructor. Allocation in implicit copies is a common source of death-by-1000-cuts performance problems. kj::String, for example, is movable but not copyable. When you include such a class in a larger structure, it breaks the ability for the outer class to derive a copy constructor automatically (even an explicit one, or a private one used by a clone() method). What's the best way to approach this?
- _pmf_ 6y agoI also absolutely love the kj style, but it's so different from basically everything else that I have a hard time incorporating anything in my daily work in the cess pit.