C++ Core Guidelines
github.com
github.com
However, in some respect this list also shows what is wrong with C++: The fact that you even need such a huge set of rules to write safe and efficient code is somewhat unsettling. I'd rather hope a new version of the language standard would at least allow to hide deprecated constructs behind a "safe/unsafe" pragma.
You could:
- prevent people from using deprecated features
- immediately remove TONS of boilerplate
- automatically generate header files from the cpp's
- have high level keywords that would generate design patterns for you (no more having to re-implement a Visitor for the first time that year and having to spend a day debugging it)
At the same time you could still leverage C++ libraries and when you do need to write an allocator or do some fancy diamond inheritance friend method templating magic shenanigans you could always drop back down to C++
EDIT: What I find kind of amusing is that with time you can do more and more of these things through Visual Studio's magical "wizards" and checkboxes (maybe it's the same for the other IDEs.. not sure), but ofcourse it's rather silly, clunky and slow to use. In the latest version you can finally make matching declarations/definitions with a couple of clicks - saves a lot of the mildly frustrating copy/past'ing!
As for library and tool support, I think a safe subset of C++ couldn't actually use tools for "real" C++, because the languages would be too different.
Yes, maybe Rust is better, and maybe if we could push a button and make everyone use it the world would be a better place.
But the reality is that there are very few code bases written in Rust and an uncountable amount written in C++ - there are decades of work behind C++ and decades of work will still be done in C++. I need to earn a living TODAY and work in the current reality, and I need tools to help me be more productive. I can't wait 5 years for some brand new language to take over.
Most libraries use a C ABI due to it being stable, and Rust has fantastic FFI support for C (although tools to take in a .h and spit out the FFI in Rust are definitely needed). There's no performance cost to FFI in Rust, either, it's just a function call. As someone that spends a lot of time in an existing C++ codebase, FFI with zero overhead offers a very enticing migration path.
But as you were asking about making a new language, I was just pointing out the language you were starting to design has already been built.
OTOH if you are really after C++ but done right then that would be D http://dlang.org
If you wish something like this for C then I would likely point to Nim.
In the end though I never had enough time to bring it to a usable state, it was just a toy project for me to play around with waf-build and boost.test.
It doesn't compile to C++, but directly to machine code.
"Use exceptions for error handling only"
"Avoid data races"
"Prefer a for-statement to a while-statement when there is an obvious loop variable"
"Don't pass structured data as strings"
"Eliminate redundant indirections" (for performance)
and so on. These don't really lend themselves to automatic checking, but it's still nice to see them written down in one place along with the more language-specific stuff.
(For example in duck typing, rather than checking whether your value is of a certain type, just assume it is, and deal with possible exceptions if it isn't)
for example
drawline(int,int,int,int); // obscure
drawline(Point,Point); // clearer
this is only "clearer" until you find yourself at the very common case where you use several libraries where each defines its own "Point".Now you're writing something like
drawline(Point(vec.x1, vec.y1), Point(vec.x2, vec.y2))
Pretty ugly!And of course drawline(Point, Point) isn't clearer at all imo. What's the coordinate system? What are the units? Any performance notes on this function? Etc. I'll be checking the documentation the first time anyway.
void do_something(vector<string>& v)
{
string val;
cin>>val;
// ...
int index = 0; // bad
for(int i=0; i<v.size(); ++i)
if (v[i]==val) {
index = i;
break;
}
// ...
}
this is shown to be inferior to the find() version. Again, it's only inferior until the very common case where you're doing something else with the index, which is pretty common during debugging. Then you'll be rewriting the above "bad" version anyway.Similar problems other higher order functions like for_each().
Plus, a lot of problems are solved by namespacing and something like this:
typedef std::tuple<int, int> Point;Good question. Where are you going to look that up? In the documentation of drawline? Should drawrect, drawcircle, etc..., also include documentation about the coordinate system/units? Of course not, that information should be centralized. That's where Point come in.
And that's just a detail, the main advantage of using a class is that is abstract details. In the Point class, you may want to switch from int to int64_t without having to revisit all the methods.
This kind of nonsense is just a recipe for everyone spending lines of code and neural cycles on a bunch of junk that will just be unpacked back into the underlying representation anyway in order to be used in the real world.
Don't do it. Simplicity trumps almost everything in real engineering. Abstract where abstractions add real value (which usually means "hide non-trivial amounts of code) and not where they do nothing but "document" stuff.
void drawLine(lib1::Point, lib1::Point);
void drawRectangle(lib2::Point, lib2::Point);
In this case the usage is clear (and IIRC I think most IDE's will avoid omitting lib1:: and lib2:: in compiled code even if you do). The whole point of namespacing is to avoid the name collision issues that are (were?) inherent to C.But the second you need to match that abstraction to the code from some other developer which doesn't use it, you need that underlying representation back again. And this kind of mismatch isn't an uncommon edge case, it's the pervasive truth of modern development.
Basically: C++ can't win here, and it would be better not to try IMHO.
But that's kind of a problem: while the underlying representations might be perfectly compatible, and you might know that for sure, that is not at all guaranteed. If you have a function that takes (int, int, int, int) instead of (Point1, Point1), Point1 = (int, int), that would not help you very much when Point2 = (int32, int32) (e.g., sometimes libraries make assumptions about internal representations, sometimes they don't).
Using low-level primitives everywhere like that can be a very misleading kind of simplicity - it might disappear. That C++ makes you reckon with the possibility that Point1 /= Point2 is supposed to tell you something about what you can know for sure about those types.
This kind of scenario isn't uncommon: https://developer.apple.com/library/ios/documentation/2DDraw...
If both libraries just passed two doubles around, you wouldn't be able to catch this at compile time.
That's the point. The compiler is useless here, and this kind of glue code is probably 60+% of what developers are actually writing in practice.
But this site wants us to sit down and write a ton of boilerplate to handle a "Point" abstraction (complete with a full operator suite I'm sure, yada yada) just to solve a problem that can't be solved in the first place.
And if the libraries do know about each other then it's even better, because you can just say
CoreGraphics::Point cgp = CoreGraphics::Point(0, 0);
UIKit::Point p = UIKit::Point(cgp);
and p could already be in the right coordinate frame.Anyways, this isn't some constant thing. Java developers typically like abstraction, ruby guys like passing raw hashes and strings/symbols everywhere. My preferences change based on language, codebase size, and how many people I'm going to be working with.
drawline({x0,x1}, {x2,x3})
True, though you can usually reduce the pain by implementing appropriate conversion operators and/or implicit constructors.
> Again, it's only inferior until the very common case where you're doing something else with the index, which is pretty common during debugging.
You can use subtraction to obtain the index from the iterators in the better version. In this case, find really is superior.
drawline
You can always define an implicit constructor: Point::Point(const Library::Point &p)
Of course, you're now hiding a ticking time-bomb from a the next maintainer of that code.If you absolutely want the latter case, overloading is your friend:
drawline(int x1, int y1, int x2, int y2);
drawline(Point p1, Point p2) {
drawline(p1.x, p1.y, p2.x, p2.y);
}
This, in my opinion, is the most conducive to refactoring (and modularizing code).As an aside, you really should be doing:
drawline => drawlines
"Where's the one, there's many."That information can be encoded into the class type, in which case you get compile time checking. I wrote a blog post about this some time ago:
http://www.randomprogramming.com/2014/06/quote-of-the-week-t...
The summary is that we had two different coordinate systems in play and had many bugs caused by passing values in the wrong coordinate system around. By making the different coordinate systems different classes everything became explicit and checked by the compiler. We completely eliminated a whole class of bugs from our program.
The great thing about using an int is that it can be anything (integer). It can be an x coordinate, or the number of eggs or a selection between multiple options.
The bad thing about using an int is that it can be anything, and sooner or later you will pass the number of eggs into a function that really wants an x coordinate.
This. I did the very same thing when dealing with some code that was frequently converting between three coordinate systems. Using Haskell code duplication was close to non existent and still type-safe. Not wanting to sound too fan-boyish ... but that was one of the first times that I realized that a good type-system can help you prevent whole classes of bugs.
Distance d = feet(7);
d += meters(3);
std::cout << d.yards();
Distance d2 = 3; // I won't compile
This assumes that you want to mix units, not defeat it. It all ends up being very efficient; use MKS for the underlying representation. You can multiply, add, and so on, quite efficiently; the only conversions happen on input and output.Same problem where each library defines its own "String".
Would be nice if the standard libraries would define such basic types and everyone would just use the same ones.
This might work better without OO kitchen sink approach to types. Just standardize a simple data layout or interface and leave the possibility for libraries to define their own functionality in extension methods.
Only with C++11 did they add std::u16string, which is at least suitable for the (horrible) UTF16 encoding. Maybe we'll get an UTF8 capable std::string class in another 20 years.
Still, as Herb Sutter asked at CppCon 2015: "How many of you have your own homegrown string types at your companies? ... Don't lie. ... Right. And those of you who didn't put up your hand and are just bashful, ... we know we have them." [1]
We have Scott Meyer's effective c++, more effective c++, effective STL, effective STL, modern effective c++.
I have read Bjarne's "A tour of C++", which is a good one to read. Also read his "design and evolitution of C++", which gives rationale for what we see the ugliness of C++.
I think we need a small book that describes the history of C++ until now, such a book can help us to remember "C++ core guidelines". This is similar to how great vowel shift in the history of Enlgih langauge helps us to see deeper patterns in English pronunciation.
I hate a bunch of guidelines, without historical explanations, because such guidelines are very hard to pin to one's brain unless one is working in hardcare C++ everyday. Maybe, this sounds like playing Piano scales:)
It's great to see C++ getting an ecosystem and Bjarne's excellent writings help a lot.
Jonathan Blow has a good point about smart pointers being a poor solution tacked on the language. As it wraps your class in another class and becomes kinda ugly.
After using Java, Python, C# and other languages. Smart pointers aren't really that smart. I'm not saying garbage collection is the solution - but smart pointers aren't a silver bullet.
What is not smart about smart pointers? std::shared_pointer implements reference counting just as OP describes.
I clearly didn't say it was.
> They're often implemented poorly (the only good one being Go's) and they're slow.
Do you have any evidence to support that? From my experiences and tests C# has been on par with C++ [1]. Of course it might be slightly slower but not enough for me to dismiss it. Java I think is a little bit slower - but my problem with Java is its memory requirements.
> Garbage collection also necessitates a runtime environment to keep track of the data.
Assuming they do this in a separate thread and you have an OS that schedules threads/processes in a sane way and the GC doesn't need to interact with the main thread - you should never notice a performance hit in your application.
> What is not smart about smart pointers?
They don't track the actual memory usage. John Carmack implemented his own version of memory management for both Doom 3 [2] and Doom 3 BFG [3] presumably so he could get debugging information that isn't available with the C++11 smart pointers. I've implemented something similar to Doom 3's heap where I track the line and file of where the heap memory was allocated.
The one thing I've noticed about developers today is that they have become lazy. Yeah - smart pointers will work correctly in simple cases and 90-99% of the time but it was written by humans so it may have bugs in it. Most C++ developers today refuse to run valgrind because they just assume their program doesn't leak memory when using shared_ptr/unique_ptr. I have personally found memory leaks in pretty popular open source projects. I would have told them about it - but I've found most C++ communities to not welcome new people and their ideas.
[1] http://www.codeproject.com/Articles/212856/Head-to-head-benc...
[2] https://github.com/TTimo/doom3.gpl/blob/8047099afdfc5c973faa...
[3] https://github.com/id-Software/DOOM-3-BFG/blob/9c37079c16015...
Is this the same "Doom 3" that came out in 2004, a full seven-ish years before c++11.
[1] https://isocpp.org/wiki/faq/freestore-mgmt#ref-count-simple
But I am pretty biased, I loved reference counting, and ARC just made it more awesome. I want ownership, I point strongly. I don't care about ownership, I'll have a weak ref and that's it.
Bring a formal proof that you won't go out of memory. Attach additional information on how you wrote an allocator that runs in constant time and does not risk taking it's sweet time to scan a huge area of memory. Sure, it's all O(n), but who cares if determining when you are going to hit the huge n case is equivalent to solving the halting problem.
Of course, at this point, the hassle simply means you don't use dynamic memory.
Even in the case that you're using an STL container, it's probably a hash table or tree data structure, and you're probably populating it at initialization time with a defined quantity of items. In that case, even if it's doing heap allocation it's effectively static allocation.
Whatever float your boat (and your particular niche).
In theory, doing without is a huge problem, because you're running the risk of resource leaks and stale identifiers - but in practice, neither is a huge problem. Picking these sorts of bugs out has been pretty simple in every project I've worked on. I've had far more heartache from the bugs stemming from RAII-type mechanisms holding on to resources longer than they should, than I've had from fixing bugs caused by resource leaks or stale resource identifiers.
I respectfully disagree with leaks not being a problem.
RAII on the other hand, totally.
Some examples: - making all constructors explicit and adding an implicit keyword instead for that rare case - automatically generated !=, <=, >= etc from == and < (with an explicit override when you want to be silly) - removing some of the redundant syntaxes (const positioning, typedefs vs using etc - removing the class keyword
It's the little things in life that make it so great.
If you want to master the whole thing, sure. But for in-the-trenches application devs, it's a lot simpler now. For example, if you want to disable copying:
MyClass(const MyClass &) = delete;
That's a lot simpler than knowing the declared-private-but-not-implemented trick. There's a decent-sized list of these simplifications, especially using 'auto' instead of remembering the 102 characters necessary to describe the return type of 'begin()'. for (const auto& item: collection) {
is probably my favorite feature of C++11. Ironically it makes the recommended approach of using standard algorithms with lambdas look abstruse in comparison. Considering your example, I still feel that inheriting from boost::noncopyable is the cleanest way to declare the class noncopyable. It feels almost like a language keyword!But the amount of new gotchas is significant too. In particular to use move semantics effectively you have to be aware of such obscure things as xvalues, std::move_if_noexcept and rules for autogenerated move constructors. The fact that the committee itself did not get it right on the first try speaks for itself.
It's because C++ is a disaster.
Everyone likes to point out that you should only use a subset of the language. But that's not my experience with C++; only using a subset makes you vulnerable to all the problems associated with not understanding the actual language that is implemented, not just the subset you wanted it to be.
Yes C++ carries decades of history, but since C++11 even if you can still make use of this you don't have to cope with. It is perfectly fine to use only a subset of C++, you just have to use the right one and this is what this documentation is about IMO. Yes you have to understand the language to be efficient with it, but it is a language that allows high performance and maintainable large scale software.
Python was cited as an example in this thread and I don't get it. There a many ways to write the same thing in Python and I always end up spending more time looking on the web to figure out what is the "Pythonic" way.
On the other hand, not following some of these C++ guidelines can mean your code is subtley broken and depending on undefined behavior, or leaking memory, or accessing something it shouldn't, etc.
I write C++ at work. We all know the language pretty well. We send people to conferences. We have somebody presenting at CppCon next week. We still get bitten by C++ more than you would belive.
In any case, even compared to other systems languages, C++ is a mine field. It's sad that C++ and C are so entrenched that you've even equated systems and fast and unsafe, when unsafe really doesn't belong there.
Look at Ada. It performs just as well as C++ and is/was routinely used in low level embedded systems and safety critical systems. It's widely regarded as safe and doesn't have the problems C++ is so often associated with. It was first standardized in 1983 and C++ is just now getting around to adding some of the stuff Ada has had all along.
And then there are the newer, relatively unproven languages like Rust, Go, and D.
I do enjoy C++ a lot, but it payed an hefty price for C copy-paste compatibility.
And there's Rust: it's a pleasure to work with and has almost none of C++'s problems.
I am excited about Rust as well, and look forward to using it professionally in the future.
The real problem is your simple subset is often built with the very difficult features your attempting to avoid using. So by running from complexity you just embrace it, but don't learn that you're actually working with virtual templated necromancy until 2-3 months later when you try to extent the production code base.
I did two relatively big projects (years apart), with the last one using Qt (because I also needed to do a GUI), and despite it working and doing what was supposed to, I wasn't entirely happy with the quality of the code.
Oh man, I miss those days, ha ha.
You know you've finally reached peak C++ post-traumatic stress disorder when you walk into an interview and someone says "what does 'int x=5;' do?" and you spend half an hour trying to figure out what manner of hideous deception lies behind that simple expression. Your brow starts to sweat, your hands shake, and finally you exclaim "Undefined!".
"The rules are designed to be supported by an analysis tool. Violations of rules will be flagged with references (or links) to the relevant rule. We do not expect you to memorize all the rules before trying to write code.
The rules are meant for gradual introduction into a code base. We plan to build tools for that and hope others will too."
So it sounds like they will be introducing some tooling in the near future. It'd be nice if they leveraged existing tools (like clang-tidy)
[1] http://www.vatican.va/roman_curia/institutions_connected/lat...
its a shame we are ruining C++. can't we just have something better... :/
(I didn't downvote you, but I think you're being downvoted because you said that C++ is being ruined without explaining why these suggestions are problematic or arguable, which doesn't add to the discussion)
the usual examples of it shortening iterators are not a problem i have day to day... i also find arguments about typing less and avoiding typing mistakes seem to miss what programming is really about in the wild. i spend 20% of my time at absolute most actually converting my ideas into code and typing them into some document...
for(const auto& elem : container)
is vastly more readable than for(my_long_type<specialised_type>::const_iterator iter = container.begin(); iter != container.end(); ++iter)
I recently had to rewrite some C++11 code to C++98 to fit into a project. Using nothing but auto and range-based for loops, it nearly doubled in size by switching back to the older more verbose syntax. So long as you don't go to the other extreme, these have much value.debugging that typoing a callback signature results in nothing happening in asynchronous code controlled by the ppl task library is a massive pain for even an experienced programmer.
auto is nice in principle, but it is introduced in such a powerful and dangerous environment that there are subtle gotchas in real world use cases. including targetting a major platform by using its system libraries.
programming is about thinking, not typing and programmer convenience. i don't enjoy auto for that reason either... if it were to save me time it would be little. i find using the stl algorithms and avoiding the nasty stl iterator pattern like the plague means the problem you describe just doesn't exist for me as a day-to-day case.
If so, could someone link me to a resource?