I've developed quite a bit in C, C++, Python, Go, Ruby, and Java/Coffee script and a hand full of others.
The thing is, of all the languages I code in C++ is my favorite. It is the most performant (using C where necessary), and is as easy to code in as JavaScript IMO. C++14 (and higher) feels like coding in Go or Python.
I feel most people don't actually have experience enough with modern C++ and are trying to use the 90's version of C++. The 90's version is awful, and even the modern has a few quirks. However, when you need performance and something less than C it's amazing.
There are no channels, there are no lightweight/green threads, there's no standard HTTP library, no standard crypto libraries, no standard test framework. For certain classes of applications this makes Go significantly more productive and significantly less bug/error prone. Not to mention compile times.
Even the nicest things in C++14 suffer from backwards-compatility-ism i.e. awkward syntax, and endless incomprehensible garbage output from the compiler when you make an error e.g. inside some complex templated code. As C++ devs we manage to live with that stuff but it still sucks.
Even if your code uses some nicer subset with standard libraries you typically need to interface to a whole range of things that do not comply with that subset. Be that something like a Cassandra driver that uses libuv or OpenSSL with its native C interface. Any C++14 beauty that may have existed is gone and your code is now subject to all those subtle memory management, buffer overflow, and concurrency issues.
I used to think garbage collection was garbage but I've start to appreciate how much simpler it makes my life (Even over RAII smart pointers) for very little cost in Go.
Python, Ruby and Javascript are a completely different kind of language so I'm not sure it's worthwhile comparing those to C++. There are many things you can do in Python that you can't do in C++ and that's mostly a good thing ;)
That said there are certainly some classes of applications where there's no substitute to C++. It's also much nicer than it used to be. But it shows its age.
It sounds like you're doing web dev and for that I wouldn't use C++. As you pointed out, languages have different advantages and disadvantages. I was just trying to point out most people don't seem super well versed in modern C++ so it gets undue hate.
I usually build embedded systems, desktop apps, and even some higher end computing applications. I use QT in conjunction when I need most of that stuff you mentioned, as that framework makes it easy. Also,
> there's no standard HTTP library
I mean, there isn't a standard library, but cURL is easy to use.
I'd classify the systems I work on as "cloud"/systems work. So not exactly what you'd call "Web" but close enough to the kind of use cases Google designed Go for, i.e. their infrastructure. We've managed to make C++ more or less fit better through mountains of in-house libraries and code.
I've used C++ with Qt in building embedded applications and my experience was mostly good. Then again Qt had its own types which were incompatible with C++ and a fair bit of work was getting more or less standard C++ mated with Qt. I remember writing a lot of glue code that used Boost.Signals2 ...
On one hand it's true that people who still see C++ as C with classes and don't use things like lambdas, algorithms, smart pointers, STL data structures, are contributing to some hate/misunderstanding of C++. On the other hand, C++ developers who do use the modern features get used to all the quirks and don't understand why others don't see what we see ;) So we just maybe std::move, or inherit from enabled_share_from_this without thinking twice and know the difference between && and & and the new lambda syntax but really, as a developer, most of the time, why should I care? To some extent a camel can't see its own hump.
What you're saying is basically exactly what I would have said 2 years ago before I started using Go more heavily. Now it's mostly C++ because I have to and Go because I want to... Not that Go is perfect but the stuff that it does well it also does easy.
I get the problem, but I don't think experienced C++ developers should pamper inexperienced (or unwilling to learn) C++ developers by avoiding features that are considered best-practices.
Template programming with Boost.MPL? Sure, I regret every time I used that - I've gained little, and very few developers will be patient enough to learn how to maintain this code. But avoiding std::move() and and R-values just because other developers may get confused?
By all means, if your developers are unwilling to learn modern C++, use Go - it's easier to learn. I agree that's it particularly well geared for any network IO-heavy server. C++ can give you better performance, but it probably requires 5 times the effort.
What percentage of machines would have it? I doubt it's higher than 1% even for programmers.
I also didn't mention earlier that curl is probablly not a replacement for an http library. There is some overlap in use cases but neither can replace the other.
I completely agree with you. The lack of a module system (still missing in C++17) and of standard libraries that are commonplace in other languages (testing, string formatting...) are the two most serious drawbacks which are preventing me from using C++ anymore.
The fact that there is a nice subset of C++ which works well is not a valid argument, in practice. As you say, you are forced to include in your projects a lot of libraries, each assuming its own definition of what this subset is, and the union of those subsets can be a monster.
You can grab Catch as a test framework. It's a single header file you can include directly in your directory and it takes like 2 minutes to learn how to use.
Thanks, I didn't know about Catch. However, my point is that this kind of tools should be part of the standard library, like it's the case for other modern languages.
One thing that always pissed me off in the old days was on one side we had nice libraries like Turbo Vision, OWL, VCL, and then we needed to resort to pure C like code for accessing a database via ODBC, or some image manipulation library, for example.
So for some sanity, I always spent some days writing sane, type safe, C++ wrappers.
But this always fails flat when working in teams that don't share the same quality goals, without directives from above.
I full-heartedly agree. My general impression as someone that has learnt most of C++ afted C++11 is that the biggest drawback to the language are colleagues who either want to "just write C" (and in my experience also tended to riddle whole projects with resource leaks) and/or suffer from "that's not the way I learned it, thus it has to be rubbish" as their lead mentality.
Thought-trough, modern C++ code is IMHO quite often more readable, elegant and generally more pleasant than anything I've seen in C# or Java so far. But it's all hampered by the poor code some people ham-fisted in before or afterwards...
So, at least for me, C++'s biggest drawbacks are many of its users, not the language itself. Interestingly, that's something thad e.g. the Python community seems to have way more success managing, although Python as a language checks almost all boxes that should - in theory - foster quick hacks...
C++11, 14 and 17 finally made me fall in love with C++. It's almost like having a nice and easy scripting language with the added bonus of static typing and a huge performance boost.
There are a few things that I'm still not quite happy with.
* It's still incredibly cumbersome to set up a project
* Setting up a proper build system is tedious, especially if you're targeting multiple platforms.
* I'm missing some "functional" methods attached to the collections, e.g. find, filter, map, some, each, ...
Afaik some of them exist as functions that take 2 iterators and a lambda but that's just plain ugly and unreadable. Instead of
Have you tried CMake for setting up you C++ projects? It hide pretty much all the dirty details of find libraries and linking against libraries.
If you already have a compiler installed, you can go from nothing to knowing how to build a library, executable and an installer for multiple platforms in just a couple of hours with the CMake tutorial: https://cmake.org/cmake-tutorial/
I agree that CMake is better than handwriting a Makefile, it is still absolutely shit compared to Rust's build system or the straightforwardness of sourcing external dependencies with Maven or Gradle in Java.
If I want to use an external library in a C++ project, I need to:
* download it from the website / GitHub
* build it from source
* add the library into my CMake file.
Whereas in Gradle, I add a single line to one file and the library is installed on my next build attempt. Full stop. There are no more steps.
C++ makes it searingly painful to get a project going. I'm honestly afraid to add any new libraries to my projects because I know that (at minimum) that means I'm sacrificing an afternoon to resolving new errors.
Although I generally agree with you (CMake is a pain), I wouldn't say that this problem is caused by the language itself.
That you have to compile anything yourself is a byproduct of the fact that different platforms have different ABIs, combined with the lack of proper package management on platforms like Windows.
What I find the most enervating about CMake is that somehow everyone agreed (at least to some degree) to use the worst cross-platform build tool in existence.
Instead of a build system based on solid principles we got a "hack your own build" starter kit in which you have to look for dependencies yourself ^1 combined with a build script language that is worse than the languages it set out to replace.
How can anyone honestly be happy when switching from makefiles to this monstrosity?
^1 And no, the FindX scripts never work properly everywhere.
Personally, I agree about CMake and its horrible scripting language, and I like Scons a lot more. It's Python-based so it's easy to work with and flexible, and not nearly as ugly as CMake.
I think CMake took off, because it has no dependencies, works with any IDE and can be extended to do anything.
I have been in places where demanding python just wouldn't work. Either a specific version was needed for something else and managing the dependencies was a pain or we were already reliant on other scripting languages. I think this kills any other language as a build tool.
There are zealots for every IDE and some IDEs require special project files. If your build system doesn't seamlessly integrate most then it couldn't have made it. Scons never worked with IDEs I liked, Know how to get it cowkring with QtCreator? Other will demand vs2015r3 integration, how do you do that?
Then then there are the build systems you didn't mention, but the minimalistic ones. There was even one that was entirely stateless, but it was also too inflexible for most real builds.
CMake very carefully walked a thin line that dodged all these issues and the fact it is a giant pile of global mutable shared state simply doesn't for all but the largest builds.
I have tried to look at CMake a couple time and always found it very...inflexible? Currently I have a hand-written Makefile which:
- automatically collects executables to build from the .cpp files in two distinct directories
- uses the CPP preprocessor to only link in the object files in the rest of the project associated to headers used directly or indirectly by the executable .cpp files
- allows me to select the compiler using an environment variable
- allows me to pre-define some common options for the three supported compilers (e.g. to disable specific warnings with ICPC and increase the template instantiation depth for Clang)
- allows me to specify whether I want to rebuild everything or just a single executable and whether this should be copied into the external directory contained in my $PATH
Yes, putting this together was a bit of work (208 lines for the Makefile, 34 for the dependency generator and 15 for the script which puts the current output of `git diff` into a CPP #define), but now it only rebuilds the things needed, only links in the object files needed and allows me to quickly test multiple compilers. Getting all this with CMake seemed extremely difficult when I last looked at it.
Addendum: I should add that my only external dependency is Boost and LAPACK and I essentially ask the user to point me at their Boost base directory and plug in their LAPACK link line -- this works well for the target audience (fellow physicists who are used to all sorts of horrible toolkit installation processes), but I understand it may be annoying to the more general public (or if you have more external dependencies).
If anyone wants me to test anything in particular, I've got that RC on my laptop - although the setup for 2017 is much nicer than anything that came before.
Unified call syntax would have allowed something like a function of the form f( list, condition ); to be called as list.f( condition ). I don't think it made it into C++ 17 though. It's too bad because that would have allowed some really powerful extending of existing code. C# has this already. I hope that it makes it into C++20
https://isocpp.org/blog/2016/02/a-bit-of-background-for-the-...
Also, you can do stuff like
auto blah = with( list ) << sort( ) << filter( predicate );
now. It's not in the std lib but it is doable
C# doesn't have unified call syntax, it has extension methods. You're right in that they do allow you to write a function that takes an argument and call it as if it wasn't on that argument, but it doesn't have UCS generically, just as a special case.
You are technically correct, the best kind. The ability is similar though, although I can see some fun C++ code with templates to apply it to all types. Then, if allowed, something like int i; i.print( ); could happen.
Although I agree that it would seem natural to have such a method in a standard container class, I would like to make a couple of observations.
First, there are containers that do have a find() method, std::map, for example (although not on a condition). It could be a wrong impression, but to me this tells something. Perhaps, if I intend to do a search, I might be better off using a different structure (rather than a list) that is more suitable for searching, or maybe a different - more efficient - algorithm (such as binary_search(), if applicable).
Second, if you use a container as a part of your own class, defining a find() method is trivial.
I have no doubt that there are other, more technical reasons for the standard library being the way it is, for example, the idea of separation of algorithms from containers by means of iterators.
Find probably wasn't the best example but even with find, I sometimes want to look for an item in a vector with 20 elements. In cases like that, I'm fine if find() has to apply a comperator to every element until it finds the first that matches.
And functions like map (apply function to each element of a collection), filter (return new collection with all elements that match a condition) and possibly some others have to be applied to all elements in a collection anyway.
> Second, if you use a container as a part of your own class, defining a find() method is trivial.
It is, but I'd rather not have to define 20 trivial things when I start a new project.
Yes, except the canonical map is—surprising the users of other collection frameworks—an ordered, balanced tree. So std::map::find() is O(ln n) while std::unordered_map::find() is O(1).
Yeah I'm pretty excited about it. Though I'm curious, and maybe someone more experienced can help me out, how it will change performance. Because won't this be a little slower?
> * Setting up a proper build system is tedious, especially if you're targeting multiple platforms.
There is a tool called waf (https://github.com/waf-project/waf) which is getting way too little love, but is really the perfect build system for C and C++ projects:
I do use CMake as my build system, It is like torture ! literally, I always liked waf, I am curious why not many people use it ? it is incredible, I have very high hopes for waf and Meson.
As a user who primarily uses SCons, I feel like my opinion of Waf is reasonably fair.
1. Developers trying to use Waf never realized that they were supposed to check-in the python BLOB into the repository rather than using a system package. Consequently developers would find a lot of regressions in the syntax for builds.
2. Developers had to add the root of the project to their ${PATH} for it to work properly. This may not seem like much... but if you don't know better, it takes people by surprise.
3. Build scripts simply broke between Waf versions too often. Perhaps you can blame this on package mantainers... or maybe distribution maintainers.
4. There was never an overwhelmingly compelling case for `waf configure`. `cmake CMakeLists.txt` borrowed syntax from the C preprocessor which people were comfortable with. `autotools` had thousands of recipes for `./configure`. And adding configuration options to `scons` via command line options always worked just fine.
5. Using the Python language encouraged developers to implement actions which were not idempotent resulting in changes which were not properly tracked by Waf.
6. The code provided no obvious benefits to SCons aside from a little speed, and it was light years slower than CMake, autotools, or simple GMake.
1 & 3. You are supposed to ship the waf script with the code. It's 100kb so it's not a huge burden to check it into your repo. I'm very sure it is explained in the waf book: https://waf.io/book/
2. Either run "chmod +x waf ./waf build" or "python waf build" Adding directories to $PATH isn't necessary.
4. I think the use case for configuring and building with the same tool is very compelling. Like:
This will add a configure check using pkg-config ensuring that gtk+ is installed. Then due to the use declaration in the call to the program() method, the correct compile and link flags will be added.
Because it is all in one tool, it is very easy. Other build systems complicates it by keeping configure and build separate.
The built-in rules are nice but have you tried doing anything fancy with waf? I wanted to define a rule that would transform an input file into a output file using a tool that was built from source at build-time, then use that output file as a dependency for a unit test. After spending a full week mucking around with waf, I gave up.
CMake was much easier for me to understand. Sure, the language is hot garbage, but the mental model of how it functions is simpler. I did something similar to the above and it took an afternoon of learning.
I would agree with you except for build times. It's not anywhere close to JavaScript or Python in terms of iteration speed and prototyping.
C++ 11 and 14 don't do anything in terms of build times AFAIK -- they probably make them worse. I'm hopeful that modules will make a difference, but at first glance they seem awfully complex.
And also metaprogramming. In Python, I use Python for metaprogramming. In C++ I have to switch to this weird template language. I'm a template user, and they are mostly great to use, except for build times. Template authors are a completely different kind of C++ programmer.
When you find that C++ templates are taking unacceptably long compile times, you often benefit from declaring an `extern template`. Some times I have CPP files which do nothing but declare `extern template` instantiations for types I infrequently change.
This was added in C++11.
In the header you state something like:
template <typename _Tp>
class Example {
};
extern template class Example<int>;
In some CPP file you write:
template class Example<int>;
At that point any calls to Example<int> will not be resolved by callers. They will simply expect the template to be available at link-time. Theoretically this could be slightly slower (I think), since method calls will cross translation units rather than being inlined. But it does cut down on compile times.
> Note: The extern keyword in the specialization only applies to member functions defined outside of the body of the class. Functions defined inside the class declaration are considered inline functions and are always instantiated.
>"I feel most people don't actually have experience enough with modern C++ and are trying to use the 90's version of C++."
I understand what you are saying but for a newcomer it's hard to discern the differences as in "oh thats a very 90s way or thats a very C++ revision xx way of doing things." I think this is part of what makes it so overwhelming to learn. The scope of the language is already quite large and then when you pile the different versions on top of that.
I am curious if you could share any advice on that for a newcomer to C++?
Is it practical to only learn the most recent revision/standard? I would thing its good to know the good and bad if only to understand what rev or style you are looking at without being confused. But maybe that's not necessary or practical?
The problem, as Bjarne also complains about quite frequently, is that regardless of what we do to advocate modern C++, that "90's version of C++" is what most developers at companies scattered around the world bother to write.
But template metaprogramming is not at all what is meant by modern c++. We're talking about using smart pointers and lambdas with the algorithm library and other major conveniences (like simplified multithreading), which are all really great since C++11/14.
I know, still there are quite a few things that are possible, like not using C strings and vectors, making use of RAII, references for out parameters and wrapping C like APIs in stronger types.
These were already possible even before C++98 got released.
I don't get people describing C++ as their favorite. My favorite language is one that is the most productive for the problem at hand. What language/framework/environment gives you the best set of tradeoffs if you want to:
- Code new work or updates in as little time as possible
- Let a large group of devs of varying skills produce a high quality code base with the least amount of overhead/rules/babysitting
- Achieve the the best defect rate/quality given finite time
- Require the lowest cognitive load to reason about things
- Performance is high enough to meet a point of diminishing returns given the problem at hand
I keep hearing about Rust when this particular wishlist comes up. So far I didn't have the time to try it, but from your post I'm guessing you did. So in what way did it break for you?
For me personally, a few things seem broken, but there are work-arounds. Some things are very confusing for me, and I might get past those.
However, I work with some really smart people (Masters and PhDs in math, physics, engineering), and very few of them have patience for programming language complexity. They can mostly get by with a subset of C++, but it's not optimal. Rust is just not simple enough, and the "overhead/rules/babysitting" would be pretty hefty. I wish that was not the case.
That's a strange comment. You don't understand why someone would enjoy or have as a favorite C++? Just because it doesn't work for you, it shouldn't be awesome for someone else?
The point is that when goals are pragmatic it's hard to find room for subjective preference.
Do people "enjoy" C++ because they weighed the important concerns and thought it was the best objective choice for their work overall? If so I understand it, otherwise we're talking about a hobby language.
Somewhat off-topic, but I was wondering if you could explain something about your phrasing. This is a serious question, not snarky. I think I've been out of the loop on this one:
What do you mean by "most performant"? Is there some meaningful difference between that phrase vs. "fastest"?
"Performant" is basically just a buzzword but I've always viewed it as encompassing a bit more than "fastest" - something like "most efficient in real world use".
C++14 (and higher) feels like coding in Go or Python.
C++17 is so full of good stuff I'm considering beta compilers just to get it. Soon Python and Ruby won't have any advantages left in writing beautiful, clear, correct, bug-free, and easy to read code and C++ will still run 200x faster.
Garbage collection could prove to be a dead end. Rust and Swift are prospering as the latest cool things because they don't have any. Decades from now we may be hearing about garbage collection as a vastly expensive experiment that simply didn't pan out.
The clever combination of declarative management for common cases and reference counting for complicated cases looks like the winner in the long run.
You can't just redefine a word that a person has already defined (albeit implicitly), in order to make them wrong. That's not how being right on the internet works. If you want to be right on the internet, you have to define the word before they do.
I think Raymond Chen once noted that garbage collection just aims to give the programmer the illusion of infinite memory under the assumption that only finite amounts of memory are every in use at once, while also absolving them from having to release memory manually.
So if your program allocated less than available, doing nothing would be a valid GC strategy as your program never hit an upper bound in what it can allocate.
I found that an interesting way to think about the problem because it's more concerned with what GC enables instead of what it strictly does. The former is always the same, no matter the GC implementation, but the latter can vary considerably (doing nothing, reference counting, mark-and-sweep, full concurrent and compacting GC, ...).
> garbage collection just aims to give the programmer the illusion of infinite memory under the assumption that only finite amounts of memory are every in use at once, while also absolving them from having to release memory manually.
They're possible, but not nearly as common. Back when I was coding C++ full time I spent most of my debugging time chasing down memory leaks and out-of-scope variables. Then in a decade of Java programming I think I had one memory leak.
But that's a two-edged sword. Many Java developers, especially those recently out of school, who have never had a C or C++ background, think the GC is a magical blanket that takes care of everything, and as a consequence they don't have an intuitive grasp of the true costs of memory. They just throw things around like it's no big deal. I've interacted with a lot of C++ developers who were hired to rewrite large Java applications in C++ for performance reasons, when really Java could have handled it if only the developers understood memory better.
GC is an abstraction that can work well but can also interfere with how a developer thinks.
> I've interacted with a lot of C++ developers who were hired to rewrite large Java applications in C++ for performance reasons, when really Java could have handled it if only the developers understood memory better.
Yep, this is a big issue.
Another good example is copying arrays of primitive types with for loops instead of using intrisics like System.arrayCopy().
Or using lists instead of arrays.
Sure there are many situations where there is no another way other than step out of the JVM into C++, but many could be avoided with better code as well.
I add the remark here: in some literature, especially older, Reference Counting is not listed under Garbage Collection, but under Automatic Memory Management or Automatic Storage Reclamation.
See page 6, remark 7 in 'Uniprocessor garbage collection techniques', Paul Wilson:
In C#, GC is part of the language semantics and its interaction with unmanaged pointers is well defined in the language semantics standard, including how the memory is pinned when unmanaged pointers need to access GC allocated memory.
My point is that you're arbitrarily drawing a line between core language and its standard library, that is meaningless in practice.
If reference counting is a kind of GC, and the standard library offers reference counting, then the language offers a kind of GC - same as if the language itself had a built-in refcounted pointer. There's simply no practical difference here.
No, because there is nothing in the language that requires making use of that specific library.
It only makes sense to talk like that, if the language semantics define a relationship between the language and library requiring the library to always exist as a form of runtime and how the use of that runtime relates to language constructs.
If I can just throw away the library and replace it by something else where those types aren't available and still make use of 100% of the language features, this relationship doesn't exist.
That's splitting hairs to the point where the difference is not relevant for any practical purpose.
There's a single document defining what is ISO C++. It defines both the language and the standard library. Therefore, to "write in C++", by default, means to write code that uses and relies upon both the language semantics and the standard library functionality. Which means that std::shared_ptr etc is readily available, and is the standard way to use refcounting-based GC in C++.
Hence, ISO C++ is a language with optional refcounting-based GC.
Only partially, in Swift, it is still up to the user to cleverly work around scenarios that the system does not catch. Which is why you have weak references and other many tricks in the language to circumvent infinite reference cycles and related problems. It's hard to call language garbage collected when you have to deal with all that stuff yourself. Just look at the stuff you have to do sometimes in your closures to avoid them creating memories leaks.
C++ has a GC ABI introduced in C++11 and the dialects C++ Builder, C++/CLI and C++/CX do have GC support at language level.
Library types like std::shared_ptr don't really count to say that a language has a GC, because their use, specially their correct use, is not enforced by the compiler.
Isn't not having garbage collection and advantage?
I mean we have destructors which handle so much more than garbage memory.
Close files, release mutexes, close sockets, cleanup thread pools, manage the lifetime of anything. One time I opened a web browser in the constructor and closed it in the destructor, and I had full confidence that no matter what crap happened in the middle my app never leaked whole browsers.
Memory fragmentation? And alloc/free aren't free. So now you have to hack together your own memory management scheme and hope it performs well enough in both areas to be worth the extra effort and lost safety.
Oracle's JVM have compacting GC that solves memory fragmentation, but Python and Ruby certainly do not. Python and Ruby interpreters also do a lot more dynamic allocations, so if memory fragmentation is ever a problem, they suffer worse.
If you're thinking like that I don't see why you wouldn't prefer Java or C#? One of the major advantages of C and C++ is explicit memory allocation and lack of GC pauses.
A person could wish for a more compact and lightweight runtime than what Java and .Net offer while still having GC. I imagine some of those people are checking out Go nowadays.
Most languages with garbage collection also have a mechanism for deterministic cleanup, e.g. IDisposable in C#, or AutoClosable in Java. That's because when dealing with external, or non-memory resources, deterministic release is a good thing. That doesn't mean that that scheme has to be the only option for memory allocations. Garbage collection has a lot of benefits, too.
No garbage collection. But, modern C++ recommendations pretty much deprecate manual memory management for 90% of situations. std::vector and std::make_unique for 80% of everything; other containers and make_shared when you really have to; malloc and new for specialty situations.
Firstly, what exactly is the difference between make_unique and plain old unique_ptr? Why do I need to include a separate template to use the former?
A related issue I faced today. Say I want to create a std::vector of objects. From what I understand, I have three options.
1) Use raw pointers, but be sure to cleanup using delete.
2) Use shared_ptr, which will ensure that your object is safely deleted once no one is referencing it.
3) Create a stack allocated object, then push that onto the vector. The vector will create a shallow copy of the object, and the original copy will be deleted at the end of the current scope.
In this scenario, I wouldn't be able to use unique_ptr, because that would mean that both the vector and the local scope have pointers to that object. Am I correct?
Are there other options for achieving this that I don't know of? Also, which one is the best approach for the general case? I ended up using shared_ptr and it seems to be working fine... I think.
> Firstly, what exactly is the difference between make_unique and plain old unique_ptr?
The make_unique function uses variadic templates to take the arguments to your constructor:
auto oldway = std::unique_ptr<Foo>(new Foo(1, 2));
auto newway = std::make_unique<Foo>(1, 2);
The new way has the small virtue of eliminating one more place where you ever need to call new. However, it also offers the library writer more possibilities in the internal implementation. For instance, they can nest your type in another struct for accounting purposes without copying or additional dereferencing. However the second advantage is more relevant to shared_ptr, which is probably why C++11 had make_shared, but not make_unique (added in C++14).
> I wouldn't be able to use unique_ptr, because that would mean that both the vector and the local scope have pointers to that object.
Yeah, if you're using unique_ptr, the pointer is uniquely owned in at most one place at a time. For your situation, I think the vector should probably own the objects, and you borrow them with references, iterators, or pointers (via the get() method). Be wary of holding on to those references though (the Rust fans will be quick to point out the dangers here).
A fourth option, which you should consider unless your objects are expensive to copy, is to make your objects copyable and not use unique_ptr or shared_ptr at all. I think it's much simpler to reason about objects as values than it is to reason about objects as smart pointers or (dumb) references.
> I ended up using shared_ptr and it seems to be working fine... I think.
Go with what works for you, but shared_ptr makes additional promises about thread safety and interactions with weak_ptr that increase its runtime costs. I wish shared_ptr was simple by default and that there was yet another smart pointer type for when you need those additional features.
The biggest reason to use make_unique and make_shared is to avoid the ancient C++ problem with non-deterministic order of evaluation. In code like this:
the only guarantee is that `new foo` and `new bar` will occur before the corresponding shared_ptr constructor, and all of them will occur before the call to foo. This means that the following is a valid sequence:
1. new foo
2. new bar
3. unique_ptr
...
Now, if constructor of bar throws, you've leaked foo.
With make_unique, construction and smart pointer acquisition is a single atomic operation - if it throws, everything gets cleaned up.
I think technically you could use unique_ptr and transfer ownership (move) when pushing into the vector, but the underlying "bad pattern" here is that you almost never want to have a vector of (smart) pointers. The vector should own the objects directly.
> Say I want to create a std::vector of objects. From what I understand, I have three options.
Vectors already own their contents, so there is no need to use unique_ptr here. The objects will be allocated on the heap either way, and you don't need a stack object either. Just push or emplace the object into your vector and be done with it. When the vector is deleted, so will its contents, automatically.
> The objects will be allocated on the heap either way, and you don't need a stack object either. Just push or emplace the object into your vector and be done with it. When the vector is deleted, so will its contents, automatically
I guess what you mean to say is that emplace_back will do the allocation and lifetime management. But using push_back on a stack-allocated object is going to result in a copy.
emplacement doesn't handle lifetime management; the vector itself does this, whether you emplace or push.
push_back with a stack object will copy the object, yes. Emplace_back with a stack object will also, they are not much different. But if you emplace by creating the object inside the emplace call itself, then there is no copy.
it more like 99% of the time and as long you don't go near new or malloc you generally can't leak.
Then with RAII you can clean up any kind of resource. Most languages have pretty rudimentary tools for closing files and worse for releasing mutexes. These tasks are almost easy to get right in modern C++.
I still love 'with-open-file and its sisters in LISP. Get that resource in one line and it's guaranteed to be cleaned up right without ever thinking about it again. Then you have total freedom to use it however you want and the results destructure themselves.
"With" is even better in Python. Exceptions during closing work right. The big problem with RAII is that if anything goes wrong in a destructor, you're in trouble.
With C++ since C++11, there is little need for garbage collection. I haven't written a manual memory "new" or "delete" in years, thanks to unique_ptr (which in my benchmarks is somehow also faster than manually managing the memory).
std::shared_ptr works, but fyi often there's a slightly better alternative. For cases where the target object is not shared between asynchronous threads, mse::TRefCountingPointer[1] is smaller/safer/faster/more scalable. For cases where the target is shared between threads (while being modified) mse::TAsyncSharedReadWriteAccessRequester[2] is safer.
> Probably people who are having more complex data than a single tree.
Reference counting is safe with a DAG (not just a tree), but I get your point. For the times I've needed an arbitrary graph data structure, I used an adjacency matrix to store it.
The generator pattern is really not that fundamental a language construct, and, in general, is quite straightforward to implement in languages with powerful enough abstractions. C++ is one such language: http://www.boost.org/doc/libs/1_62_0/libs/coroutine/doc/html...
I wouldn't call it straightforward, boost.coroutine requires custom assembler for its implementation. Then again, stackfull coroutines are significantly more powerful than generators available in most other programming languages (notable recent exceptions are Go and Lua).
Boost.asio provides stackless generators [1] implemented purely in C++, by (ab)using the preprocessor and the switch statement.
I think there exists both necessary complexity and unnecessary complexity. Necessary complexity is when the underlying system is genuinely hard and trying to simplify it causes nuance to be lost -- Rust and Haskell are good examples. Unnecessary complexity just stems from poor design and then perpetually trying to add features on top of the poor design and making it worse -- the classic example is PHP.
IMO, C++ has both a lot of necessary complexity (due to its lofty goals) and a lot of unnecessary complexity (mostly due to the C back-compat). I think this is the root of a lot of the "C++ sucks!"/"C++ is great!" arguments; when people only see one half or the other.
My bottom line with C++ is that it tries and mostly succeeds to do a lot of hard things, but has questionable design decisions that make these things even harder than they need to be. For a while we just had to live with that because if you wanted a language that was both fast and high-level, C++ was the only game in town. I think that's no longer true, which is why I'm such a cheerleader for Rust: I see it as trying to have all the good things (necessary complexity) of C++ without the baggage.
IMO, C++ has both a lot of necessary complexity (due to its lofty goals) and a lot of unnecessary complexity (mostly due to the C back-compat). I think this is the root of a lot of the "C++ sucks!"/"C++ is great!" arguments; when people only see one half or the other.
C++ is more like an ecosystem of about a dozen languages. You can divide it into 4 languages chronologically. You can add a lot more by categorizing by use. If you pick and choose properly, you can set up a subset of C++ that makes it a great language. Half the computer programming managers out there have below average taste, however.
Any sufficiently large project that pulls in dependencies will likely involve you dealing with all 4 of them.
This is one reason why I doubt adding more features to C++ (making it 5) is going to make the language any more pleasant to use (outside toy, one person programs).
Any sufficiently large project that pulls in dependencies will likely involve you dealing with all 4 of them.
Which describes my current work situation. However, this would seem to imply that a startup or a small project would be able to set up a C++ project with a beautiful, cohesive subset of C++. Your point is something that people who are serious about language design for programming "in the large" should definitely pay attention to.
This is one reason why I doubt adding more features to C++ (making it 5) is going to make the language any more pleasant to use (outside toy, one person programs).
How do you distinguish between one, two, three, or four person projects? Can you really make such a blanket statement about all the different possible codebases that exist? Methinks you exaggerate.
(And I say this as a former dyed-in-the-wool minimalist Smalltalker!)
I put the distinction at "more than 1" because that's the point where you need to deal with code that isn't your own.
(In theory of course your buddy could use a style and feature-set really close to your own and make it all a nice abstracted API so you don't need to look inside etc, but you get my point)
I put the distinction at "more than 1" because that's the point where you need to deal with code that isn't your own.
You've never worked in a small shop where there was a good coding standard and everyone stuck to it? That sort of thing actually does pay off. I've seen this in my professional life.
This doesn't even address the problem at all: you still have to deal with the non-C++11 code to write the facade (and fix bugs in the underlying code).
I don't mean it never works, just that is it seldom the case.
I know C++ since the C++ARM days, it is one of the languages I like most despite its warts, teached it to first year students before C++98 happened.
Nowadays I spend my work hours in Java and .NET stacks, but still do occasionally use it, when needed.
I never worked in a company, with exception of my time at CERN, that actually followed C++ best practices.
So yeah, there are lots of companies making good use of C++, as proven by CppCon and C++Now talks, but they seem to be the minority, when you take into consideration the ones whose main business is not selling software.
At least I was pleased to see the BMW presentations at FOSDEM, where they mentioned moving from C to C++14, as a means to improve safety on car systems.
So yeah, there are lots of companies making good use of C++, as proven by CppCon and C++Now talks, but they seem to be the minority, when you take into consideration the ones whose main business is not selling software.
I should be more thankful about my current job, then. I guess a good heuristic for judging a language is to go to conferences and see what is portrayed there, then "dividing by 8." That will get you to about the level of the average shop.
People keep forgetting about this. Yah, starting from scratch, using the exact compiler you want, and using only modern features is awesome.
It's dealing with an existing code-base or team that doesn't agree on what "awesome" is or supporting old compiler/platforms or 3rd-party dependencies that turns C++ into a nightmare.
Basically, C++14/17 is an awesome language when working on your own thing, in a vacuum, without any dependencies.
Fwiw, I think a team that's too aligned in taste and similar issues tends to write bad code. Resolving differences, while sometimes painful, very often leads to better code. Both stylistically and architecturally. That does, however, require a respectful team and the ability to compromise.
On the other hand, in other languages I will be dealing with only one sublanguage, which usually happens to not have the third party libraries required for my program.
There's something to be said for the language designer's trick of picking a well populated language platform and either bootstrapping from that, or having easy callouts to another more popular language.
You make a good point in nuancing the different types of complexity, and trying to sort the complexity derived from doing something hard from the complexity of doing something wrong. But you skip the important details we do not have an objective way of knowing which is which. You are asserting that Rust/Haskell have more the of former and less of the later without really explaining how you go to that conclusion.
Rust and Haskell are complex languages (as compared to say C). And it's not clear for a lot of people that their complexity is any better than C++. But by trying to garanty an ever increasing level of correctness and safety, they booth creating a large set of computation that are conceptually easy but very hard to express in a way that is correct to the type system/borrow checker.
This is myopic in the characteristically self-undermining way that most of these defenses are.
I actually like modern C++ well enough for the niche described here; lambdas (and the control they offer over captured variables), type deduction, ranged fors and the like make it pleasant enough to write for being as performant as it can be. I've written many more lines of C++ than Rust. But it's still apparent that language-enforced invariants are quite a different species from conventions that will enable safe programs if everyone uses them correctly at all times. You can maybe guarantee that for a team, but can you guarantee it for several teams? Can you guarantee it for every external dependency?
I can imagine plenty of pragmatic analyses today ending up with C++ as the ideal choice of language, but the monomaniacal focus on Performance ignores ecological concerns like these and also mostly doesn't grapple with the fact that there's plenty of low-hanging fruit to get to performance-wise in any project before something like a fundamental 1.2x performance penalty on Rust's part really matters, especially considering how domain-dependent that number is likely to be. Pure speed is a pretty poisonous obsession in our field, IMO, and I think we've reaped the whirlwind for it already. It's time for a more holistic outlook.
I agree that most of the time you should trade performance for coding faster, since fast computers often allows you to release a working solution in much less time.
Meanwhile, there is also this tendency of software getting slower, and this other tendency of code getting trashed because it was written as a short term solution.
C++ is designed for high quality, meaning taking more time to write a better solution. It's more thought towards industrial long term quality than market quality. I guess a bad analogy would be engineering versus maintenance. There is often much less engineering to do than maintenance.
I spent some time working on HFT systems. These things needed to achieve end-to-end timings (from a tick being DMAed into our memory to placing an order) around xx microseconds at the 99th percentile. The bulk of the code was written in a language that forced us to have bounds checking everywhere, and that was totally fine.
I'd assume most array boundary checks can be optimized away by a good compiler anyhow, assuming the array semantics allow that in the language in question.
The cost of bounds checking is very manageable with modern CPUs and compilers. (I guess the bounds checks execute in parallel with the cache lookup into the array).
Firefox now uses it in release builds (for vector-like objects) for security reasons, and IIRC there was no real performance impact.
Even back in the Algol 60 days, it was manageable.
"Many years later we asked our customers whether they wished
us to provide an option to switch off these checks in the
interests of efficiency on production runs. Unanimously,
they urged us not to--they already knew how frequently
subscript errors occur on production runs where failure
to detect them could be disastrous. I note with fear and
horror that even in 1980, language designers and users
have not learned this lesson. In any respectable branch
of engineering, failure to observe such elementary precautions would have long been against the law."
That's a good point, and I didn't mean to parrot FUD by implying that that multiple definitely exists; more that, even if die-hard C++ boosters are right about it existing, it's still not the ace in the hole it's made out to be given all of the other factors that likely overwhelm it.
I work on high-performance scientific simulation software in C++, and, I have to say, it works really well for us.
Our competitors do a bunch of code generation to accomplish the same things in Fortran that we do with templates and policy classes.
Sometimes, for shits and giggles, I'll prototype algorithms in Haskell or Common Lisp or what have you and, I have to say, the experience isn't really all that compelling, and this is without introducing MPI and all the associated headaches of working with MPI in something it doesn't support first-class.
I used to write scientific code in C++ too, but since a few years I've switched to Python+Fortran using f2py to automatically create bindings. Although there are a few drawbacks (different conventions in indexing arrays is the most annoying), I have not regretted this decision, python's ecosystem is incredibly large, and Fortran plugging Fortran modules allow me to optimize both speed and memory consumption efficiently. (But I eagerly await for the day Julia will be mature enough to allow me to switch!)
I only use Fortran just for low-level number crunching, and I must say I have yet to miss C++ templates here for this kind of stuff. I'm curious, what is your typical use of templates in your simulation code?
> what is your typical use of templates in your simulation code?
Apart from templating over floating-point precision, we use templates and policy classes to do a lot of the code reuse / flow control you'd use run-time polymorphism for, but without the overhead of virtual function calls. This is pretty useful for us, because our simulation algorithm has a lot of sub-parts that can be done in different ways. Rather than having a bunch of switch statements (or virtual function calls) in inner loops, we can have that switch statement once, in a factory object, and return instances of objects templated over the correct policy class so that we don't have to branch in inner loops.
One of our main competitors is on the Fortran / Python plan. Our application has, instead, a few proprietary input formats (one XML-ish, and the other binary to be used with a GUI). This has its drawbacks (i.e. rolling and using our own Python-based macro language for use with the XML-ish input files). The CEO tells me, though, that this is actually kind of a godsend from a support perspective, because there is a point where all of the customer's simulation logic is frozen into a text file, which we can then debug (this is in contrast to the Python / Fortran approach, where the customer's simulation logic may be very tightly coupled with our own code).
> and this is without introducing MPI and all the associated headaches of working with MPI in something it doesn't support first-class
Have you looked into a language like Julia or a framework like Charm++? I've seen these used in research papers, but I'm curious if these are used out in industry.
It's possible to get around this using __restrict__ to tell the compiler what it needs to know to optimize. Which is, I have to admit, kind of a pain sometimes.
Yes, yours is one of the use cases where C++ really shines. You really do need that combination of low-level performance and high-level expression throughout your codebase.
I found C++ very complex and difficult to work with, though it is very powerful in terms of performance. There is also several years of libraries available in C++ that is difficult to leave behind.
If you are looking for something like C++ in terms of performance and has a nicer syntax, you should try the D programming language. It's very nice to use, as fast as safe C++ code (you can write unsafe C++ that is faster), has stronger typing, templates, mixins, and can interface with C /C++ libraries.
I've moved my personal projects over to D, I've started writing a game with OpenGL, SDL, ASSIMP, Devil and it's much easier than C or C++.
I've not used D on platforms other than x86 but I have heard cross compilation mentioned in the comments of some conference talks. I found this page which has the status of the LDC compiler on various platforms. It looks like it is still under development but I don't know recently that page was updated.
I think C++ templates have hit a turning point where you can push much more static type information into your program and the compiler errors are increasingly useful.
For instance using the json for modern c++ json parser, you can write this:
myclass c = json_node;
and it compile time statically checks that a json->myclass deserializer exists, and gives sane errors if one of the (recursive) subfields of myclass does not have a json deserializer.
This makes json processing in python look like blood letting, and is close (until runtime) to the experience I'd expect from ocaml and other functional languages.
C++ type inference and first class lambdas and flexible typed tuples have made it possible to create a real useful type system into C++ programs now. It's quite pleasant.
But it robs a lot of much slower languages of their best value proposition. You can now do it all in C++ with correctness guarantees and still run 10x faster than most strong typed systems.
Recent languages like Go and Rust have taken over a lot of mind share—at least on sites like Hacker News
C++ isn't "good" or "bad", it's just OLD (and is backwards compatible with an even OLDER language).
We are continually learning which language abstractions give a lot of implementation leverage (e.g. garbage collection), and which introduce unnecessary complexity.
Any new(er) language has the benefit of incorporating those learnings into the language design.
a lot of implementation leverage (e.g. garbage collection)
Putting garbage collection in that basket is controversial and lately the evidence is piling up against it.
Notice how new important growing languages like Swift and Rust don't have garbage collection and programmers don't miss it. GC is more and more restricted to low performance languages like Python, Javascript, and Ruby and a few specialized highly tuned and expensively engineered exceptions like the JVM and obscure LISP engines.
And if you've ever tried to program responsive, interactive, predictable, or time-critical systems, you know that GC is ruinous and awful for those applications. Meanwhile languages like Rust, Swift, and C++ can be used for everything from systems programming to games to low power mobile apps to scripting.
Swift does have garbage collection! It uses reference counting, and AFAIK it doesn't have cycle detection, which means it's up to the developer to ensure that no cycles occur.
Regarding responsive interfaces: pure reference counting is particularly problematic for this, because an unbounded amount of work may happen every time an object's reference is decremented (because the object might be freed, which causes other objects' references to become zero, etc). If this happens inside a critical section Your Life Is Pain. At least you can control when this happens by taking and then dropping an additional reference. (See Obj-C's allocation pools.)
C++'s shared_ptr<>, BTW, is also reference counting, and suffers from the same problem. So it's technically correct (which is the best kind of correct) to say that C++ has garbage collection too.
When people say garbage collection they usually mean a tracing GC.
You can twist words and say that shared_ptr means C++ has 'garbage collection' but I doubt anyone would agree with you if were were to word it like:
"C++ has a garbage collector"
Ref counting is a form of 'garbage collection' in so far as it is a way to release memory, that's about it. I certainly wouldn't consider Rust's Arc<T> a 'opt in garbage collector'.
The problem is that "garbage collection" as a term seems to have become synonymous with "tracing GC" (or, in other circles, "whatever the JVM does"). I've started using the term "dynamic lifetime determination" to refer to both tracing garbage collection and reference counting (with our without cycle detection, though the distinction matters). The question then is how pervasive a given language makes dynamic lifetime determination. In Java (GC) and Python (RC with CC), it's ubiquitous. In Go (GC with value types) and Swift (RC without CC), it's pervasive. In Rust and C++, it's selective (note that a large C++ codebase with shared_ptr everywhere would, effectively, be garbage-collected (to say nothing of e.g. Boehm)).
In contrast, C's automatic storage duration for stack-allocated items ( http://en.cppreference.com/w/cpp/language/storage_duration#S... ) and Rust's system of ownership for heap-allocated items are things that I'd consider "static lifetime determination".
> The problem is that "garbage collection" as a term seems to have become synonymous with "tracing GC"
The term has been used that way since I first heard it back in the '80s, so it's probably not a battle worth fighting at this point.
Yes, of course, there's a formal sense under which reference counting is just an outlying position within the broader world of garbage collection, but from an engineering point of view, it's a significantly different set of tradeoffs, and reference counting doesn't really qualify as the same thing when you're deciding how to structure your program.
> When people say garbage collection they usually mean a tracing GC.
Wikipedia lists both tracing and reference counting as forms of GC... hell, when interviewing at Microsoft I was asked to describe the two primary families of GC, expecting tracing and ref counting.
More like, you're free to ignore the common-sense suggestion to examine the algorithms used as they pertain to your use case, just because someone once said "In theory these two things are equivalent". But it won't help you once you hit the inevitable edge case.
Terminology is useful right up until it isn't. Resting on definitions rather than seeking proper descriptions is a great way to fail spectacularly.
You're doing exactly what I'm talking about, you're trying to show how the authority you consider authoritative is right, not trying to understand how in a very real sense, reference counting also is not garbage collection because it lacks some critical feature from someone else's point of view.
If you're right, you're just some guy referencing an authority. Yay you. If you're wrong, you're totally ... all over the point of what someone else is saying. Instead of trying to force us to see how you're right, why not try to see how someone else isn't wrong?
Reference counting isn't garbage collection. It's the opposite of garbage collection. With reference counting, which is optional in C++ and can be opted out of in Swift, you keep track of your allocations and release them when you're done with them. With garbage collection an outside program decides when you're done with memory and deals with it for you on its terms.
It's true that there can be unlimited work done in reference counting. Of course, it's true there can be unlimited work in any function of any program. [0] With reference counting a programmer can fix the amount of work to be done and when in deterministic fashion. With garbage collection, there is no such power. The gc decides when it will work and you cannot control or predict the interruption.
You cannot produce serious interactive or time sensitive control software like that. You certainly can't write system software like that.
How about: Reference counting is one form of garbage collection, but "Garbage Collection" without any additional qualifiers or context usually implies "tracing garbage collection", with or without the aid of other GC algorithms.
In common usage, I don't hear of languages like C++ described as a "garbage collecting language". That usually describes Java, Python, etc.
I could say this in response to a dozen posts here, but arguing over the strict definition of the term is largely beside the point, which is the actual capabilities of the various languages. In fact, the argument itself shows that the distinction is no longer either as straightforward or useful as it once was, making the dichotomy moot.
yourself: Kind of, but only because C++11 defined a pluggable GC ABI.
This is the sort of discussion that is far more fruitful than a pedantic argument over the precise meaning of a term of art in a field that is rapidly advancing. And if the academic community really is obsessed with this, then that is unfortunate.
If the runtime mechanically detects that an object will never be used again, without you having to explicitly say so (and woe unto you if you're mistaken), that's what's meant by garbage collection. Reference counting does have a deterministic cost, but it's extremely high and applies to everything you do. Copying collection benefits from deferring and batching the work when most objects die quickly and can be ignored with zero effort (just recycle the arena containing all of them).
In order to know whether an arena is empty, you need to have walked, at a minimum, each object that was in the arena. That applies for copying collections too as during the copy phase you've walked and inspected each object that might be copied into the arena at least once. Arenas do not reduce the cost of garbage collection to O(1). And in any event arenas are not solely the preserve of sweeping or sweeping+copying collectors.
A reference counting collector that _cannot_ break cycles (like in Swift) might have lower algorithmic complexity. I'm not sure.
The biggest bone of contention with reference counting is the constant adjustment of the reference count of objects on the stack. OTOH, the bone of contention with sweeping collectors is constantly walking all extant objects, including ones not directly on the stack.
There are various optimizations you can make to one or the other or both; many arguments about how real-world scenarios tax one or the other model; and many arguments about which is easier to optimize. But at the end of the day reference counting is no worse in terms of algorithmic complexity than sweeping. What matters most will be the quality of implementation. Is the compiler smart enough to know that an object passed 3 routines deep doesn't really escape the stack scope? If it can do that, it can elide all the cost of either approach.
Thanks for the paper touching on reference counting work queues, I hadn't paid much attention to that approach since proponents tend to focus on instantly reclaiming each dead object. I still find semi-space copying (move O(live) objects and ignore O(dead) objects where dead >> live) to be not just one alternative but an enormous win I'd almost never forego, since buying more memory is so much easier than buying more CPU and bus throughput and has been for years.
I have trouble believing that behind-the-scenes escape analysis is very effective on real-world code. Look at how much painfully explicit source annotation Rust found they needed.
What matters most will be the quality of implementation. Is the compiler smart enough to know that an object passed 3 routines deep doesn't really escape the stack scope? If it can do that, it can elide all the cost of either approach.
This is the plan with Swift and what makes it zippy.
bone of contention with sweeping collectors is constantly walking
The real problem is that you cannot determine when the program will pause. It makes interactive and soft realtime impossible with gc. (Hard realtime should not run a generic allocator at all.) You can't write anything but servers and unpleasantly unresponsive apps.
Android developers have to carefully jump through hoops to avoid allocating any RAM at all if they don't want to bug their users. For years when the chips were slow, ref counting was a major competitive advantage for iPhone. A hundred-billion dollar win.
And then there's system software. You can't afford a live, unpredictable runtime messing with you in systems code so anything that touches low level code can't use gc. There's a disincentive to really invest in a language that simply can't ever do a full stack, even if you're unlikely to need to write a device driver anytime soon. That's why no major browsers, performant ACID databases, or fast app servers depend on RAM gc.
You can write a reference counter in three short lines of code that works the same as shared_ptr<T> or Swift. A modern automatic garbage collector requires a threading library plus tens of thousands of lines of intricate code. We're not comparing like with like here.
The three line version is really easy to write. And every time you assign to a pointer, it requires uncached atomic updates to the refcounts of the old and new objects, which is staggeringly expensive compared to a block of code that can go unused for minutes at a time. And all it gets you is learning that a small piece of memory is available slightly sooner, but you'll probably want to coalesce your free memory anyway for better locality.
People tend to think that refcounting must be faster than a tracing GC because you don't have to scan the memory. The second half of the previous sentence is true, but what they forget is that refcounting needs to do a lot of things that a GC doesn't have to:
Like you said, need to update the reference every time it's moved.
malloc becomes much slower, compared to a GC language (with a compacting GC) where malloc is effectively just a single add instruction.
free also needs to do a lot of bookkeeping while the GC language doesn't need to do anything.
Multithreading introduces another set of issues when dealing with refcounting that is not a problem with a GC.
Multithreading also introduces issues for GC. It needs to stop all threads. If you look at how much work has been delivered towards the GC's of for example JVM, Go and V8. It is a really tough problem to get right for all application types.
Reference counting is very predictable. And if you see a bottle neck you can start fixing it.
And with ARC (Swift/ObjC) you get even compiler support to optimize all ref counting administration.
Further malloc doesn't have anything to do with ref counting as ref counting sits on top if it.
Further, GC languages also need to do book keeping: Generations and Compaction.
That is true, but only during GC itself. Refcounting causes these issues every time the reference is copied.
For malloc, the benefit with a compacting GC is that the free memory will always be in a contiguous segment. Therefore, a malloc operating is similar to a stack allocation in that all you need to do is to move a single pointer. Without a compacting GC, you need to search a tree whenever you want to allocate memory.
I'm surprised by this heated debate regarding GC in this thread. Are you arguing that scripting languages that use reference counting are not garbage collected? PHP used reference counting for a long time (cycle detection only made it into the language in version 5.3 if I trust Wikipedia), it feels odd to consider PHP as not being garbage collected.
For me the underlying technology is not very relevant, what matters is "do I have to worry about exactly when the resource is going to be dropped". I use the resource and when I stop using it it'll probably be deleted soon-ish. At any rate it's not really my problem (at least in a perfect world).
You say "With reference counting [...] you keep track of your allocations and release them when you're done with them" but that's not really true, I don't keep track of anything, the language or library does. I can't really tell by looking at some code in isolation when or how the resource is destroyed. The GC takes care of that.
Some languages like python, Java, Go or PHP have 1st party garbage collection that's active by default and other languages like C, C++, Rust and friends have optional 3rd party GC implemented as libraries (reference counting or otherwise).
I really don't see what's so controversial about this.
In C, C++, Rust, Swift, &c I can determine how and when references go into or out of scope and get freed.
The defining characteristic of a garbage collected system is an active outside process with priority and control over my code that chooses when to inject bugs (to wit: arbitrary pauses) into my program outside my control.
Python mostly uses reference counting, but has a garbage collector which periodically checks for cycles. It's even possible to disable the garbage collector.
Gah, another one of those promotional posts about Rust.
More seriously, the large enough Noto Sans font with a hint of extra weight renders so gorgeous in Firefox (it's even bearable in Chrome!). I wish all webpages were as readable.
As for the content, well, going back to: https://eklitzke.org/c++-java-go-and-rust I think the problem of the author is that he doesn't realize it's possible to do much better than C++. Claiming Rusts' ownership and borrow checking is exactly equivalent to std::unique_ptr or that Rusts' concurrency support is comparable to std::thread betrays very deep nativity, misunderstanding or just a mindset that can't see beyond what C++ offers.
Templates only generate maximally-efficient code in theory. Generality kills them in practice; or, it makes the template code twisted to the point of insanity. For example, there is no space-efficient way to model optional<T> for more than one value in a structure type; if you have several optional fields, it would make much more sense to be able to track optional state as single bits of one field (say) but the class structure prohibits such a space optimization and requires each template to track its own is-defined value. Pointless.
It is also essentially impossible to know that your code is well-tested: even when using something large and well-known like Boost, its authors cannot know what will happen when you substitute in 4 of your own types (my bet is “result not quite working as intended; obscure bugs added”).
I use templates but I am quite aware of code complexity and I essentially stop when the templating goes too far.
Somewhere along the line, we seem to have gotten into a mode of wanting generic code at all costs. Why? It is just fine to expose a few common specific methods when those are very helpful and clear (such as classes exposing a “string value”, “integer value” and “float value” instead of trying to provide some single partially-specialized template mess of a method that manages to still not quite work right in every scenario).
Somewhere along the line, we seem to have gotten into a mode of wanting generic code at all costs.
I know. Years ago I proposed a vector library for 2, 3, and 4 element vectors [1] as a standard. The immediate complaint was that it didn't use templates.
The trouble is that a reasonable requirement such as single-precision floating point support would require a developer to copy/paste/modify hundreds of LOC. The choice is really fork, duplicate or template.
I don't blame glm for picking the latter. Every option is painful, but templates might be a little less so.
One of the projects I'm working on could have just as easily been written in c++ instead of go.. except that in go starting the project and building it involved
mkdir my_project
# hack
go build
I wouldn't even know where to start to write a simple c++ server that uses zmq and slings json around. How am I supposed to handle dependencies and vendoring?
As much of a PITA it is trying to learn rust, getting the toolchain working and starting a new project was not one of the issues I ran into.
cmake is pretty awesome at handling dependencies; our build process grabs dependencies from git and adds them to the project. Yes, there is more overhead and a learning curve, and if you are running a simple server there are probably better choices; we use a ton of python for these things, and wrap performant code in cython.
> 20 seconds of googling turned up several answers which only require about 30 lines of C++,
OK... Now how do you turn those 30 lines of code into an actual project that you can build and cross compile? Write 100 lines of cmake stuff to build 30 lines of code?
I think if I was starting from scratch I'd try to use something like bazel.
From a practical point of view, in many cases the performance critical sections are tiny compared to the rest of the codebase. Imposing such a cognitive overhead on the programmer when 90% of the code are executed once a second just to make those 10% really really fast, and then screaming "performance!!!" is not very convincing. Just use a high-level language and drop to C when needed.
Anyway, the author later says he just enjoys writing low-level C++ code, however unpractical this may be. In which case I don't really see the point of defending his choice.
But once you've optimized those sections, the rest of the performance problems are left scattered through your code. I've had to rewrite a system in C++ just a few weeks away from shipping because even after optimizing all the performance-critical sections, we still needed that extra 15% or so of "ambient" performance.
The point about templates being easy to optimize compared to other generics systems is simply wrong. Other generics systems provide more information to the compiler, so in addition to compiling faster and providing better error messages the compiler can use knowledge of specific type classes, interfaces, modules to optimize the implementation of. For instance, Rust can optimize `type NonZeroOption<T: NonZero> = Option<T>` to just the size of `T` because it knows that the `None` sentinel value is not used by `T`. You can also have nice features like GHC's rewrite rules which allow you to effectively tell the compiler how to optimize your types.
Most of the fast binary parts of templates is from stuff like specialization, which can be added to just about any generics system. Rust and Haskell both have it.
I don't think the T: NonZero bound is doing anything there: the size optimisation is done on the monomorphised/specialised type, not the generic one. In Rust, the concept of a generic types only exist at compile time, and they have no layout. Of course, this is still an example of a generic systems that specializes for performance, in a language that isn't C++.
> Other generics systems provide more information to the compiler, so in addition to compiling faster and providing
The templating system in C++ is not generic type system, it's not even strictly speaking part of the C++ type system. It's usually define as a metaprogramming facility. Because it be used to abstract on types, it's the defacto generic system in C++, but it can also be used to abstract over definitions, expressions, computation shapes (like unrolling a loop on a factor relative to the size of a type). C++ templates are slow when compared to other generic system because of a lot of bad design decisions (compilation model and how/when a template has to be intanciated), but also because it can do more, and is used to do more.
> the compiler can use knowledge of specific type classes, interfaces, modules to optimize the implementation of.
Depends on what you mean by more information: In c++ complex type constraints can be express through template. Even more important, in you example you have to rely on someone in the rust compiler community to implement that specific type transformation. Because, the template system is extremely expressive,whoever designed the "option type" can also ship instructions to the compiler on how to specialize for NonZero type...
> You can also have nice features like GHC's rewrite rules which allow you to effectively tell the compiler how to optimize your types.
It's hard to have both, most generic type system are fast because they don't do much
as you start adding feature and smartness into the system, the performance degrades...scala compile time being a good example.
> Rust and Haskell both have it.
can you specialize on struct in rust/haskell or just implementation of traits ?
This creates a feedback cycle where people are using C++ to get high performance, and that drives the language evolution towards confusing "features" (and idiosyncrasies) that are done in the name of making it possible to generate more highly optimized code.
You can replace C++ with <insert language here> and you can replace "language" with entire programming stacks. Large numbers of programming software toolsmiths have been laboring towards the wrong goals for the past few decades. We programmers are like old school computer hardware manufacturers hell-bent on delivering the highest spec numbers at the highest margins, while consumers were beset with poor quality, poor reliability, and experience degrading bloatware.
We programmers are particularly prone to inflicting ourselves with experience degrading bloatware, while ignoring our own particular human interface design needs. We need to stop emphasizing "sexy" things like raw performance and mathematically provable semantic guarantees and start looking at how we impact our experience while reading and debugging code. We need to start calling out toolsmiths who give us "power" at the cost of long startup penalties, and unresponsive, unwieldy software tools.
> but many C++ programmers disable exceptions for performance
I have never seen a project like this for 10+something years. The only piece of code with disabled exceptions that I've seen was created for Atmel controllers. And in modern C++ exceptions is zero-cost. Your paying for exceptions only when they occur so, nobody is disabling exceptions now.
This is not the only line that bugs me but I don't have enough time for it right now.
I saw this first time many years ago. It clearly states that exceptions is a good thing but shouldn't be used in Google's projects because their code is not prepared for exceptions:
> On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.
And most importantly, I don't think that they're disabling exceptions using compiler flag. I'm sure that they just using error codes for error handling instead of exceptions.
It's hard to enjoy programming in C++ because the compile times are so long. C++ has grown up a lot, and smart pointers, a flexible template system, and lambda expressions, help you get a lot done easily, but every time you code up a useful abstraction, you risk ballooning your compile time.
Yeah, this is the genuine pain point experienced by actual C++ developers. Though really only templates will kill your compile times; other modern features are pretty safe. Modules will help. Hopefully.
> a rather weak type system, one inherited with relatively few changes from C
I don't understand this statement at all. C is a much weaker type system than C++. You can do things in C that will never compile in C++, it is much, much stricter.
> So if I try to use this template with the wrong type, after a bunch of template and macro expansion the compiler will print out a cryptic error showing the templated code and the failed type substitution.
This is one of the reasons why Andrew Sutton and Bjarne Stroustrup are trying to push C++ Concepts since C++11 [1]. They should provide much easier to understand compile time errors when it comes to templates.
C++ is an insanely versatile language, and since you can use any C library with "extern C", it has a huge amount of high-quality libraries available for it. ffmpeg, openSSL, webkit, Qt. the problem is there is not a cross platform package manager to manage these libraries, and manually downloading and compiling libraries is a hugely painful and error-prone process. it's so bad that big C++ projects like Firefox and Chromium basically write their own scripts that download and build dependencies, and simply commit the source code of the libraries they use directly into their own tree.
I believe there is some thought being put into making a C++ package standard [0], which would do wonders for the language. in my opinion half of the reason Node took off is because npm is so easy to use, and people are getting excited about Rust partially because Cargo is so well thought out. Now I might be wrong here, but I think if you took modern C++ (no raw pointer manipulation) and combined it with a good package manager, I think you would pretty much erase any advantages Rust offers.
I think if you took modern C++ (no raw pointer manipulation) and combined it with a good package manager, I think you would pretty much erase any advantages Rust offers.
I've seen this opinion now twice in short succession on HN. Issues with C++ safety aren't just a matter of a dangling pointer that should've been a unique_ptr or shared_ptr. It's massively naive to think that this would in any way be comparable to Rust.
That's not even considering the reality that no real-life C++ codebase seems to survive on those modern C++ features alone. (If it did, I presume Mozilla would've rewritten Firefox in C++11/14/17 instead of Rust. Oh wait, they already use C++11...)
> Issues with C++ safety aren't just a matter of a dangling pointer that should've been a unique_ptr or shared_ptr. It's massively naive to think that this would in any way be comparable to Rust.
While I agree, I would point out that in the same way that "safe" Rust is a usable (if difficult to get used to) subset of "unsafe" Rust, there also exist usable safe subsets of C++ [1]. Tooling to enforce that Rust code strictly conform to the safe subset, of course, already exists (and is built into the compiler). Such enforcement tools do not yet exist for C++ code, but I think they're coming. And I think they'll probably support safe subsets that are more convenient/intuitive/easier-to-use than (the current version of) safe Rust.
I wonder if adopting Rust to avoid the pervasive unsafe code in C++ isn't a little bit akin to adopting Esperanto to avoid the pervasive cursing in English. I mean, there does exist a non-vulgar subset of the English language, even if it's difficult to persuade people to stick to it :) But then, if all the cool kids are talking Esperanto...
> (If it did, I presume Mozilla would've rewritten Firefox in C++11/14/17 instead of Rust. Oh wait, they already use C++11...)
I think this may be a missed opportunity by the Firefox developers. If you look at the code, they're still using new/delete and raw pointers targeting dynamic objects. I presume that a lot the code has survived from the pre-C++11 era. I think converting their code to a safe subset of C++ (like SaferCPlusPlus) would be quicker and require less effort than a rewrite in Rust.
> in the same way that "safe" Rust is a usable (if difficult to get used to) subset of "unsafe" Rust
Where did you get this idea that "safe" Rust is a subset of "unsafe" Rust? Your whole premise here perhaps qualifies as not even wrong: this is not how safety in Rust is conceived or conceptualized.
Yeah, that's my point. Even though, unlike C++, (safe) Rust was designed to be (memory) safe from the start, it's notable that Rust and C++ end up (or will soon end up) in the same place - a "usable" safe language and an unsafe superset of that language. It's notable because before C++11, memory safe subsets of C++ could be considered not really "usable". But since the advent of C++11, there are memory safe subsets of C++ that are comparable to Rust in usability and performance [1].
I should have said I think you are operating under false assumptions, are making invalid comparisons, and are therefore drawing unwarranted conclusions.
I think you might be confused about what Rust offers and from where Rust's safety is derived. There is no "safe subset" of Rust. As far as I know, that is a nonsense phrase.
I think nonsense is rather too strong: Rust-banning-`unsafe` is a subset of the full language accepted by the compiler, and is safe. It isn't rare that people talk about the subset of Rust ignoring `unsafe`.
"manually downloading and compiling libraries is a hugely painful and error-prone process."
I think you are exaggerating. Most projects with any user base provide ready binaries which able to get going pretty fast.
"it's so bad that big C++ projects like Firefox and Chromium basically write their own scripts that download and build dependencies, and simply commit the source code of the libraries they use directly into their own tree."
I would not call this 'bad' but resilient.
"Replicating is bad" is a non value adding rule of thumb, imo.
That said, yes, it's very hard to start incorporating third party libraries to C++ when compared to any other language.
Every single C++ project of any significance I've worked on or seen in the wild has some awful dependency compilation script/process. It sucks. There are no good cross-platform solutions. Even if everybody is using CMake perfectly, you still have to glue all the pieces together.
But hey, modules are coming in the next couple years. Should really help with this.
Note that Rust's system is a generics system, and there's less metaprogramming that can be done by it. Namely, the property mentioned in the post where "So if I try to use this template with the wrong type, after a bunch of template and macro expansion the compiler will print out a cryptic error showing the templated code and the failed type substitution." does not exist in Rust, since you specify bounds in the API itself.
This is both a blessing and a curse. As a blessing, it leads to very neat, clean, APIs and you never have to worry about such template expansion errors. As a curse, some of the more complex bits of C++ metaprogramming (check out what people achieve with SFINAE) are not possible. I personally think the pros outweigh the cons, but many people from the C++ world really like the power of template metaprogramming in C++. Rust is planning on offering procedural macros which address some of the use cases, but not all.
Cleaner but far less powerful in some respects. Or perhaps, as usual, the restrictions are front-loaded. For example in Rust for some operations on a generic type you must specify the appropriate Trait in the declaration ("<T: TheTrait>"). In C++ this would be deferred until you tried to perform an operation on the type for which no valid code exists after something has been instantiated.
Other than metaprogramming, Rust's implementation can provide the same functionality that templates do, in a cleaner way in my view.
There's a Rust plugin for it. I don't use it though, I use Visual Studio Code as regular Visual Studio won't work on Mac or Linux.
It also depends a bit on why you like Visual Studio. Because it has one of the best Intellisense implementations? Much more common outside Visual Studio for languages that are not C++... Good visual debugger? etc...
A few things:
1) Writing concurrent code in C++ is exciting (this is a bad thing). Processors are not getting much faster in serial execution, but the number of cores are continuing to increase
2) Programmer time is (usually) more costly than anything else in your organization. I'd argue that for most organizations, they're not making enough money per unit of throughput to justify the excessive programming time.
3) C++ is not designed for operators. Rust has this problem too. Once you know how to inspect a Java, Erlang, or Go program, the tools become familiar.
First, OpenMP isn’t that bad. Second, the author of the article you’ve linked forgot about another kind of parallelization, SIMD. Realistically, SSE and AVX instructions are only usable from assembler, C, C++, and Fortran.
Sometimes throwing hardware at problems don’t work. Either because it’s technically impossible (console games, mobile) or because you don’t own the hardware (PC games, desktop software). For such products, either you spent “excessive programming time” programming C++, or you won’t release any product at all.
> C++ is what you get when, at every turn in the language design road, you choose to take the path that leads to higher performance.
You have plenty of performance with C, and C++ is what you get when you enforce C compatibility at all costs, then tack on other stuff. The memory layout that classes and class hierarchies force upon you lead to sub-optimal caching behavior. Virtual functions don't have optimal performance, but they still made the cut. Pointer aliasing actually hurts performance as it prevents the compiler from doing optimizations.
I do agree with everything that was said in "The Joy Of C++", bonus points for acknowledging the Stockholm syndrome. However, pretty much all of it could be said about C.
No, they're not cache friendly. The problem is row-based vs column-based. Array of structs vs Structure of arrays. With arrays of structs/objects you get row-based access patterns. Especially for cases where you only want to access one or two columns, you always pull in everything else.
https://www.youtube.com/watch?v=rX0ItVEVjHc
SoA layout is of advantage when the components that you split into separate arrays are usually accessed separately which in most cases is not true. For most cases it is cache friendly (unless you use niche DoD).
Are there any popular languages that support SOA 1st-class? C++ is largely competing with GC languages where it is difficult to avoid separated heap allocations for every tiny bit of data.
While not 1st class, in D you can do a completely transparent library solution, because you can introspect the fields of the struct you want to be SoA and insert arrays of those fields in to the SoA'd type.
> Templates are objectively extremely difficult to understand and use correctly
Really? Template metaprogramming is not the only use valid use of templates. Most time a simple <class T> is enough and that is already a huge improvement over C.
Even most meta programming is not extremely difficult, unless we're talking boost level stuff, and more importantly it allows you to do super powerful interfaces for your API that are trivial to use (the implementation might get complex but consumption shouldn't be).
Like say you have a collection of entities where each entity can have one or more components of different types. Now you want to find all entities with components A, B, C and the returned value should have typed references to components - it gets a bit hairy to implement but it's trivial to use and efficient.
what is with programmers and needing to defend a combination of syntax, semantics, and runtime? C++ is a programming language with advantages and disadvantages. end of story.
> C++ is a programming language with advantages and disadvantages. end of story.
Sounds like the beginning of a story, during which you explain the advantages and disadvantages, determine where C++ is best used, and compare it to other options.
RAII is not superior both ways have their own drawbacks. You can forget to write finally and it may produce duplicated code. On the other hand RAII requires you to create new type just for exception safety (scope guard - which you may also forget to use). I prefer RAII but there are situations where finally leads to more readable code.
Couldn't it be some less weird, just like the anonymous functions in JavaScript and Go?
Even the lambda in Java is much readable than c++.
It give me the feeling that, c++ gives every corner a special syntax, or c++ adds new features by adding new syntaxes, there is no the word "reuse" in the process of improving c++.
In go, anonymous functions and general functions and methods have no much differences. But in c++, lambda and general functions and class methods are too different. Using class methods as callback in c++ is awful.
C++1x features make c++ code less readable, for fewer and fewer programmers know every syntax corners in c++.
> - performance is important in much of the project
At scale this always becomes true. C++ can be written with reference counting and other high level concepts with runtime penalties. When you need to optimize or scale, you will never need to look to another language. I do a lot of numerical computing in Python and often need to drop into Cython or C; in C++ this wouldn't need to happen. One the the main goals of C++ is that "there is no language below C++".
High level language constructs are always nice, and C++ has a lot of "zero-cost" abstractions, meaning you only pay for what you need. For example, an iterator in C++ can just be a raw pointer in the case of iterating over a vector, but it is more complicated for a std::map because it is backed by a tree. C++ is a high-level language, so I don't understand the argument that you would be better off splitting your stack between the "important" parts and the slow parts.
Overstated. There is a level below c++, namely assembly intrinsics. When it comes to simd code using sseN avxN you really do use them when performance is important. That's if you're staying on the CPU. Going massively parallel on a GPU? FPGA? C++ isn't as low as you go.
I'd have more time to optimize at these lower levels if I wasn't stuck babysitting C++ codebases to protect them from and fix their undefined behavior.
In my humble opinion, after C++ 17, C++ does not need anyone's defense. I had worked on infrastructure pieces in Google and Facebook. Given the scale of data, and number of machines, it would not have viable without C++. Consider running 10 times faster using C++ still consuming thousands of machines that would have been 10s of thousands of machines. However, if your application runs fine using any slower language still consume single machine, it might not make sense in that case.
We could make that sentence damning or glorifying, depending on the missing word choice and someone would argue with you about it, no matter what word.
If you're starting a new system-level project, you should be using C++ or Rust, not C and certainly not Go (which was designed for kids to write microservices with; in other words it competes with Ruby and Node, not C++). A modern language should support static typing with parametric polymorphism and statically determined object extent for most objects (with well-defined extent for the rest). Rust also gives you statically determined object access.
How can you, knowing absolutely nothing else about the use case, suggest a solution? What if they're on an architecture where the libc++ or rust runtime is low quality? What if their application needs to be NUMA aware?
There are plenty of reasons not to use C++ or Rust. They are not magic bullets which solve all engineering challenges.
The thing is, of all the languages I code in C++ is my favorite. It is the most performant (using C where necessary), and is as easy to code in as JavaScript IMO. C++14 (and higher) feels like coding in Go or Python.
I feel most people don't actually have experience enough with modern C++ and are trying to use the 90's version of C++. The 90's version is awful, and even the modern has a few quirks. However, when you need performance and something less than C it's amazing.