Hacker News new | past | comments | ask | show | jobs | submit login
My Most Important C++ Aha Moments (2006) (artima.com)
100 points by adamnemecek on June 26, 2016 | hide | past | favorite | 81 comments



> Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Yes, but that's not the real point. You can do that in other ways. Creating a new std::algorithm does that, for example.

The real purpose is letting one function be polymorphic in multiple directions. This is called multiple dispatch. Some languages support this as a built-in feature, but C++ doesn't, so a couple different design patterns were invented to fill in this gap. The Visitor pattern is the most popular and is probably the easiest to work with.

https://en.wikipedia.org/wiki/Multiple_dispatch

That is, it lets you do this:

    polymorphicOnThis->doSomething(alsoPolymorphicOnThis);
That is, for each permutation of the two types (polymorphicOnThis and alsoPolymorphicOnThis), you can define new specific behavior.


Also check out Eli Bendersky's website, he has a cool guide on multiple dispatch.

http://eli.thegreenplace.net/2016/a-polyglots-guide-to-multi...


You can use visitor to implement multiple dispatch, but that's not it's "real" point. The real point, as stated, is to allow polymorphic methods that aren't defined directly on the class.

I've seen a lot of implementations of the visitor pattern, and almost all of them used it for single dispatch.


You are right.

The Visitor Pattern doesn't provide multiple dispatch.

The Visitor Pattern relies on multiple dispatch, and so it provides a form of it to itself.

The Visitor Pattern doesn't go away in a language with multiple dispatch; its boiler plate code is just reduced.

What the Visitor Pattern is about is to bring together the benefits that arise from a cluster of ideas: first class functions, and generic functions.

In the Common Lisp object system, OOP functions are "generic functions" which stand alone and are not tied to classes. If we have a "frobosity" method and a list of objects, we can map that list easily to get every object's frobosity: (mapcar 'frobosity objlist). That is to say, we have visited each object in objlist. Each object has effectively "accepted" a "visit" from the frobosity function.

Now what we might want is instead of using generic function, to use a "funcallable object". let's use mapc instead of mapcar, since we don't need to collect the results into a list. The visitor-object itself can do something useful along those lines:

  (mapc visitor-object objlist)
This object carries state and can do useful things, like inquire each object about its frobosity and add them together.

since we don't have funcallable objects standard Lisp we can borrow the dispatch kernel of the Visitor Pattern:

  (mapc (lambda (list-obj) (accept visitor-object list-obj)) objlist)
I.e. we use a method called accept and then curry it with a lambda to simulate visitor-object being funcallable. This lambda is just using double dispatch, just like the Visitor Pattern uses its two-dispatch simulation of double dispatch.

If we have that real double dispatch, it's just more convenient to write the rest of this cruft. It's just a simple, linear bunch of definitions like:

  (defmethod accept ((visitor this-class) (obj that-class))
     ;; handle this-class + that-class combination
    )
The double dispatch itself isn't the point of the pattern. The point is to use an object as a function which is mapped over some other objects, and that object does something useful: accumulates some information, pretty-prints, translates the structure to something else, etc.


> Realizing that C++’s “special” member functions may be declared private

Don't do this anymore! Just delete them:

    MyClass& operator=(const MyClass&) = delete;
Private-really-means-deleted was a cool and very useful trick before C++11. So useful, that it was a shame to require secret knowledge and aha moments to use. So they just made it a normal feature of the language. There's also a way to explicitly use the default implementation:

    MyClass& operator=(const MyClass&) = default;


There is an obscure pitfall of making a class noncopyable by making the copy constructor private.

Consider the following class:

  class X { 
   private:
     X& (const X&); // not defined
   public:
     X bar() {
        X x = ......;
        return x;
     }
   };
Due to programmer error, an instance of X is returned by value despite the class being noncopyable. The copy constructor being private doesn't help as bar() is a method of X. This will compile and link, despite the copy constructor being undefined. I will leave the reason for this as an exercise for the reader.


It might compile, but it won't link.


It will likely link, since both clang and gcc will perform RVO on bar(), so the copy constructor won't be called.


Interesting, I didn't know about `delete`.

Come to think of it, the trick to make something private in order to "remove" it is one of these very few areas where C++ makes you pay for something you don't use, so I'm not surprised that there is now a special keyword (well, a repurposed keyword) to address that.


A declared-private but unimplemented method shouldn't have any runtime cost. =delete is just a syntactic convenience.


> Private-really-means-deleted was a cool and very useful trick before C++11

Rather, until every compiler you need to target supported this specific C++11 feature. In other words, it's still a cool and very useful trick ;)


This is my ignorance speaking, but with the gcc, clang, and msvc all fully supporting C++11, where are the holdups?


There are people which, for reasons good and bad, use very old versions of their tools. There are also C++ compilers other than gcc, clang and msvc.


And to share some of the less horrible reasons to be on older versions of tools:

You can't just download the latest version of MSVC and target the Xbox 360 or Xbox One with it - their SDK integrations aren't instantly forward compatible. Even if you could, you might fail cert - Microsoft requires you to use specific compiler versions, and specific compiler flags, in an effort to improve security. Keep in mind the 360 wasn't even x86 - just because the x86 compiler had been sufficiently tested for regressions for release, doesn't mean QA was done vetting the PPC compiler.

While Sony has been contributing upstream much of the clang work they've done to support the PS4, I don't know that everything has - and even if it has, I'd rather not spend the time to try and recompile clang myself just to embrace the most bleeding edge of compiler tech. I'd much rather just keep to whatever version Sony supplies. Some of their APIs come as binaries for C++ - I do not want to mix and match compiler versions!

...the above reasons have forced me to have multiple MSVC versions installed at the same time. Which isn't all that annoying until you target pre-release Windows 8 because Microsoft is your publisher and wants ModernUI apps, and you have to wipe - and reinstall - every few months to get on the latest version. And if you install in the wrong order, you get to restart from the beginning.

A number of game studios buy MSVC straight up instead of MSDN subscriptions for various reasons, so MSVC upgrades may cost tens of thousands of dollars (a bit under $1k per head last I checked.) Even if MSVC upgrades are 'free' for you (e.g. you have MSDN subscriptions), upgrading still involves:

1) Getting matching updated binaries for any third party C++ SDKs 2) Testing to ensure the optimizer hasn't found new an exciting ways to break your code via exploitation of (previously benign) undefined behavior. 3) Dealing with an updated IDE frontend, with all it's bugs, unupdated plugins, etc. 4) Reworking build scripts to correctly invoke the new build 5) (Re)installing MSVC across the entire studio, all build machines, etc.

Doing a VS2008 -> VS2010 upgrade of a large codebase required a rewrite of most of our build configuration, in my experience (Microsoft revamped things in terms of MSBuild.) VS2010 -> VS2012 was much less harsh - I could even share build configurations when we had to support VS2010 and VS2012 simultaneously - but getting everyone up and running again after the upgrade was still days of downtime for no gains on our end, except regaining the ability to pass certification.

More in the "bad" column: At home, I have hundreds of projects in my I:\home\projects\dead\ folder. As a result, I still keep VS2008 installed. (I also have VS2013 installed - I may skip VS2015 in favor of VS2016/2017? We'll see.)


There are tons of C++ compilers to chose from, not everyone uses only those three.

For example on the embedded space many toolchains are still C++98, not even C++03.


Icc


ICC supports delete! Auto type deduction and uniform initialization syntax are another story...


> that it was a shame to require secret knowledge and aha moments to use

But Myers had this aha moment about private constructors in 1988! Since then, this has been covered in countless books, tutorials and coding convention documents.

A newbie still has to read something in order to learn about "= delete". This is not "discoverable" just by experimenting with the language!

Knowing that delete is a keyword, and that there are declarations of the form "specifiers fun-declarator = blurb" you are very unlikely to discover, on your own, that the blurb may be the keyword "delete", and that this actually compiles and has a certain useful effect.

However, the combination of constructors and "private:" is discoverable, as Myers' 1988 aha moment shows. It's a logical combination of independent features.

It's okay for techniques in languages to be discoverable logical combinations of features rather than arbitrary syntax thrown in.

It's also okay for the culture which surrounds a language to have a body of techniques which are carried in that culture, rather than shoehorned into the language parser.


Alternative view: if you must use C++, stick to nothing later than C++2003.

Anyway, declaring a constructor " = delete" isn't the same thing as making it private. If it's private, the code in your class scope can still use it. If you can't trust your class private code to do things correctly, then you're screwed; go join IT and write backup shell scripts. So basically this "= delete" is just another ear growing out of the elbow of C++.


The reason for testing and static typing is that you can't trust yourself to be perfect. I want to declare that I won't do things that don't make sense, so the tools will tell me if I do them unintentionally.


In 1998 C++, we can solve the problem of how to make a class non-copyable, even to code that is in its class scope.

We can create a dummy class which has a private copy constructor that is not implemented:

  class noncopyable {
  private:
    noncopyable(const noncopyable &);
  };
then simply make this a data member of a class that you don't wish to be copyable:

  class whatever {
  private:
    noncopyable nc;
  };
The default copy constructor generated by the C++ compiler performs a member-for-member copy, which involves copying nc. That is not possible, so in effect that copy constructor is defeated, not unlike by "= delete".

Alternatively use inheritance to mix this in as a trait:

  class whatever : private noncopyable {
    // ...
  };


Why?


I love C and Objective-C. I can't stand C++.

Is that common among programmers?


It's easy to dislike or hate C++ due to its immense complexity, and programmers like to be able to keep simple abstractions in their head so they can concentrate their energies on their problem domain without worrying about language confusion. With C and many other languages, this is possible as they are smaller languages. C++ on the other hand: is there any language with widespread use that is so deep and complex? However, there are undeniable conveniences to C++ (safety, a good library, much more -- especially functional-style programming with lambdas now) that can speed up your work process, and if you can accept that you will never know the entire language and can just use those bits that make things easier, it is possible to have a healthy relationship with the language. And it offers tremendous room to grow as you get more and more comfortable with it. Few languages allow you to just keep diving in deeper and deeper as you desire, while still letting you get to work right away without being a master. Even template metaprogramming alone is gigantic area of ongoing research -- the stuff you can do with it is mind-bending and unavailable in nearly all other languages. Templates are, after all, one of very very few wholly functional, immutable languages out there. And template instantiation is the underlying force behind many of the modern features like lambdas that allow you to access that power without actually knowing templates well. It's a powerful language for which you must accept its character flaws in return for its abilities.

It's doesn't hurt either that the libraries available in C++ are really cool.


> Few languages allow you to just keep diving in deeper and deeper as you desire, while still letting you get to work right away without being a master.

I find it to be the case with most languages.

> Even template metaprogramming alone is gigantic area of ongoing research -- the stuff you can do with it is mind-bending and unavailable in nearly all other languages.

Your are being a bit hyperbolic. Meta-programming is not necessarily a hard thing to do, but in some languages, like C++ it was made impractical at first. Then, templates introduced some metaprogramming aspects with another layer of Turing-completeness. I can appreciate the usefulness of C++ and how templates contribute to it, though.

> Templates are, after all, one of very very few wholly functional, immutable languages out there.

XSLT too ;-)

> And template instantiation is the underlying force behind many of the modern features like lambdas that allow you to access that power without actually knowing templates well.

Lambdas roamed the earth long before templates.

> ... and if you can accept that you will never know the entire language and can just use those bits that make things easier, it is possible to have a healthy relationship with the language.

Agree.


> Lambdas roamed the earth long before templates.

I'm talking about C++ lambdas, in particular, which are templates under the hood.


> safety

Um, no. C++ can be safer than C (which is an incredibly low bar) but only if you carefully constrain yourself to certain subsets of the language, which is why the web is chock-a-block with byzantine "C++ coding standards" documents describing all the rules you have to follow in order to avoid shooting yourself in the foot.


Indeed that not only you have to follow, everyone else on the project has to follow, as well as every newcomer and every piece of third-party code that is brought in.

"Avoid the parts you don't like" works best for one programmer working alone.


I had an instructor once that insisted on having a single exit point for all loops. "Never use break or continue or return from a loop." The argument was that understanding control flow is more complex when you don't have to deal with these things.

However, those things exist in the language. You can't just pretend nobody is going to use them, because there might be very good reasons to use them, especially in an imperative context.

(Though I suppose if you're grading 100 projects, such a rule might make it easier to get the code structured the same everywhere.)


In the world outside of that instructor's classroom, there are coding conventions which forbid certain things, with excellent reasons for doing so.

Back when ISO C++ was still staffed by people with useful ideas, they invented various more specialized, safer casts that refuse to do "off topic" conversions. These replace the "(type) expr" C casting notation.

A C++ coding convention document can cheerfully forbid C style casts; there isn't any reason to use them.


>only if you carefully constrain yourself to certain subsets of the language

I'm curious which subsets are included in this?


The answer(s) to that question take(s) the form of documents with many dozens or hundreds of pages. For example:

https://github.com/isocpp/CppCoreGuidelines

So not the sort of thing one can answer in-line in an HN comment.


It's not just the complexity. It's the complexity relative to the payoff. It seems like since C++ 98, the language has accreted a ton of new features that add complexity and decrease regularity without really carrying their own weight. Templates are a good example: lots of little features to make template mets programming easier to offset the fact that templates are a poor basis for metaprogramming to begin with.


> if you can accept that you will never know the entire language

As a developer responsible for understanding what everyone else is doing on the same project, no I cannot accept that.


I love C++ except the copy-paste parts from C and the toolchain compromises used to sell it to C developers.

Dislike Objective-C verbosity and C roots, although I kind of like its Smalltalk influence.

Really strongly dislike C and the culture of insecure software it has brought into our industry.


I'm sorry, but C++ does not exactly have a culture of secure, maintainable and understandable software.


Yes, it does.

The majority of C++ programmers make use of:

- RAII

- String classes instead of char* überall

- Vector classes instead of char* überall

- new and delete instead of malloc()/free() and casts

- References for out parameters instead of pointers

- Real enums, specially since C++11, instead of #define

- Smart pointers for memory management

- Type based programming to reduce errors

- Minimize the use of the pre-processor to #include and macros, only when they cannot be expressed via templates, const, constexpr and inline.

Most of the unsafe C++ code is written by the "C with C++" sub-community that are mostly C refugees forced to use a C++ compiler.

Also while the C++ community is pushing for safety with activities like the C++ Core Guidelines and reducing the amount of UB in the standard, the C standard community doesn't care at all.

The only thing that the C community has done regarding security was the Annex K in C99, which was proven so good that it became an optional feature in C11.


> Yes, it does.

Where is this modern C++ culture that has resulted in large-scale codebases free of memory safety vulnerabilities? I've never seen even one, much less a "widespread culture".

> The majority of C++ programmers make use of:

None of this, empirically, results in safe code.

> Most of the unsafe C++ code is written by the "C with C++" sub-community that are mostly C refugees forced to use a C++ compiler.

I haven't seen any evidence for this. A lot of vulnerabilities are found in modern C++11.

> Also while the C++ community is pushing for safety with activities like the C++ Core Guidelines and reducing the amount of UB in the standard, the C standard community doesn't care at all.

I don't see any way that the C++ Core Guidelines are going to be able to succeed in creating a usable language that is memory-safe. Robert O'Callahan and I have elaborated why in other posts.


> Where is this modern C++ culture that has resulted in large-scale codebases free of memory safety vulnerabilities?

In any large scale C++ conference like CppCon.

In tooling from Microsoft and Apple.

> I haven't seen any evidence for this. A lot of vulnerabilities are found in modern C++11.

Using C++11 idioms, or C with classes?

> I don't see any way that the C++ Core Guidelines are going to be able to succeed in creating a usable language that is memory-safe. Robert O'Callahan and I have elaborated why in other posts.

Rust will become a sound alternative to C++ when:

- Libraries stop using nightly

- Rust developers can call OS APIs like COM on Windows without gymnastics. I mean with the same ease that Delphi, C++ Builder and Visual C++ integrate with COM.

- Rust developers can call OS X and iOS APIs with the same ease than Swift and Objective-C

- Has IDE support that can match what QtCreator, Clion, XCode, Visual Studio, C++ Builder, Keil MDK, ...

I really would like to see Rust one day supersede C++, but in what concerns C family of the languages, C++ is the safest option we currently have.

I have been part of the C++ community since the early days as Turbo Pascal and strong type refugee, and have followed how hard it has been for a language that is almost copy-paste compatible with C to earn the position it holds in the industry.

So until Rust gets some OS SDK love, bashing C++ community efforts to improve security won't get many adopters.

I keep on playing with Rust, but I get to write production code it still is JVM, .NET languages, Swift or C++.


> In any large scale C++ conference like CppCon.

> In tooling from Microsoft and Apple.

Microsoft and Apple regularly ship memory safety vulnerabilities in their codebases that are written in modern C++. Their browser engines, for example.

> Using C++11 idioms, or C with classes?

Using C++11 idioms. C++11 doesn't actually do anything to mitigate use after free, for example. In fact, I think there's a reasonable argument to be made that C++11 is less safe than C++03 in terms of use after free, because of features like move semantics and lambdas.

And I'm not intending to argue "use Rust" here. There are plenty of other memory safe languages out there. I'm simply arguing that C++ is not memory safe, and it cannot be while preserving any semblance of backwards compatibility. The specific technical reasons for this have been elaborated in posts that I've sure you've seen. I'd like to see proponents of the idea that modern C++ is memory safe actually try to rebut those.


The issue is not that C++ is 100% memory safe language, it is not.

Personally I would rather be using Ada or Modula-3 for the few use cases I still use C++.

However in the context of security minded developers that can only choose between C, Objective-C, C++ and nothing else, C++ is the only one providing the features to write safer code.

Of course if not all team members play ball it doesn't work, but it is better than not having an option at all.


> Robert O'Callahan and I have elaborated why in other posts.

One point of note is that O'Callahan's "Vapourware" post points out a problem that's solvable, it isn't some big blocker the way he claims it is. Your comment 162 days ago describes the real problem, or at least one of them.

Edit, because fuck:

> Where is this modern C++ culture that has resulted in large-scale codebases free of memory safety vulnerabilities? I've never seen even one, much less a "widespread culture".

The rest of your post shifts the question from "is there a culture of secure, maintainable, and understandable software" to "Is C++ software free of memory flaws." Are these concepts not distinct to you?

Why even bother replying, if you're going to drag the discussion into some corner like that?


> One point of note is that O'Callahan's "Vapourware" post points out a problem that's solvable, it isn't some big blocker the way he claims it is.

No, it is a big blocker. In fact, it's the same as the problem I pointed out, just with global variables instead of shared ptr. Aliasable mutable data is the fundamental unsolvable problem; whether it arises through global variables or shared ptr is just a detail.

> The rest of your post shifts the question from "is there a culture of secure, maintainable, and understandable software" to "Is C++ software free of memory flaws." Are these concepts not distinct to you?

Software that has memory flaws that enable remote code execution is not secure.


> No, it is a big blocker. In fact, it's the same as the problem I pointed out, just with global variables instead of shared ptr.

Imagine refactoring a program to not use globals but instead pass them up by const or mutable reference to where they're used. The checker can do the same imagining with an effect system.

> Software that has memory flaws that enable remote code execution is not secure.

Words have meaning.


> Imagine refactoring a program to not use globals but instead pass them up by const or mutable reference to where they're used. The checker can do the same imagining with an effect system.

Not at all. This requires higher order control flow analysis, which is pretty much guaranteed to fall down in all sorts of cases.


But you don't need the system to be precise or accept every correct program. Heck, you'd get pretty far with a simple conservative yes/no on whether a function might touch any globals. At least at that point you could copy-construct from a global. You could skip adding any complex mechanisms and just say, hey, every function instantiation/global variable combination is going to get a plain mutates/reads/no, which translates to how things would be if the mutable/const references was passed up from main, and if that ain't flexible enough for you, have your closure/object capture a reference to the global and we'll track it that way. [1] Of course that could get replaced with a specific annotation mechanism where we don't really put a reference in the object. But regardless, what this does is handle the problem of global variables for the set of global-using programs which you could still write, without having to think too much, if there were no global variables in the language at all.

[1] Edit: A mechanism for annotating that a class or method does not get implicitly passed globals would be very useful for things like std::function<>::operator() so that std::function objects are forced to access globals by capturing them, because totally unrelated uses of a std::function could stomp over the same type instantiation for this ultra-dumb analysis.

Edit: Here's the tl;dr version. One solution to the global variables problem is for the CppCoreGuidelines to say, "Don't use globals." Another is for them to say, "Don't use globals, but for the 90%-case we won't be dicks about it, we won't blindly pretend that every function could use globals when they obviously don't."


> Heck, you'd get pretty far with a simple conservative yes/no on whether a function might touch any globals.

No, you wouldn't. Try writing such an analysis. Either I will be able to break your analysis or it will return "yes, this might mutate a global" for so much of any reasonably sized program that the analysis will be useless.

This is basic, basic stuff to anyone who is in the sound static analysis field.


> Either I will be able to break your analysis or it will return "yes, this might mutate a global" for so much of any reasonably sized program that the analysis will be useless.

The point is, you can write functioning programs even with that -- you can copy construct out of a global, for example, but you obviously can't pass string literals to functions that touch stdout or errno. If you want to make a legitimate disagreement, argue against the more practical version where you treat different global variables as different things.

> This is basic, basic stuff to anyone who is in the sound static analysis field.

Do people in that field also nitpick pieces of rhetoric and deliberately ignore the main point?

I don't think any intelligent person in that field would argue that C++ programs can't be written or refactored in such a way so as to get rigid enough use of globals so as to pass a relatively conservative analysis. I mean, that would imply you couldn't get anything useful done in a language without globals.


> The point is, you can write functioning programs even with that -- you can copy construct out of a global

Copy construction out of a global is actually still unsafe, because some other thread (for example) might mutate the global, invalidating the this pointer. Or the copy constructor might call a function that can't be proven to not access the global, causing the same problem.

> If you want to make a legitimate disagreement, argue against the more practical version where you treat different global variables as different things.

That isn't meaningfully more precise. You are still going to require higher order control flow analysis. Think about how you would analyze functions like std::transform() and prove that the function passed in doesn't destroy the container being mapped over (or call a function that does, etc.)

> I don't think any intelligent person in that field would argue that C++ programs can't be written or refactored in such a way so as to get rigid enough use of globals so as to pass a relatively conservative analysis. I mean, that would imply you couldn't get anything useful done in a language without globals.

Well, sure, but that's not C++ anymore. Neither roc nor I have argued that you can't write some subset of C++ that is sound. In fact, there are subsets of C used for avionics and the like that are provably type-safe and memory-safe, because they disallow dynamic allocation entirely and are extremely restrictive with what pointer operations they support. But the entire value proposition of these C++ static analyses is to be compatible with the C++ ecosystem. When you start adding restrictions that rule out most C++ libraries and, worse, that you can't get around without massively rewriting their logic, then you're essentially a different language.


> Copy construction out of a global is actually still unsafe, because some other thread (for example) might mutate the global, invalidating the this pointer.

It's not unsafe, that code wouldn't pass the check. Other threads would have to not access globals at all, or all threads have to access them read-only.

As for doing things on a variable by variable basis, just imagine a rote refactoring from a global variable to non-global variable passed up from main. For example:

https://github.com/srh/memcached/compare/65da75f...235db6

We eliminate the global variable "as" by passing a struct assoc pointer parameter up from main.

The parts of this that isn't a rote refactoring is where, instead of adding another parameter to functions like event_handler and event_set, modifying libevent to pass through the struct assoc pointer, we instead put the struct assoc pointer into a closure (because this is C, whatever homespun kludge is most convenient). The other situation where it's put into a closure is some calls to pthread_create.

We know that this refactoring retains identical behavior -- every access to the global as now accesses the same as object -- because only one instance of struct assoc has been constructed. (And the kludges with C-style closures here are correct merely by inspection, i.e. the right variables are initialized, type safety would handle that.)

Now, if we have some tool for checking use of globals, it can do the same thing, and then analyze memory safety using the rules it uses for non-globals. What it needs from the user are annotations to help it know when and how a global reference should be captured by a closure instead of passed as an extra parameter to the closure. Of course, it would be perfectly valid to thread a struct assoc pointer parameter through the internals of libevent, if that was sufficient to check the program.

Now, memcached would have other problems to deal with, because the struct assoc is shared across threads. The point is, the problem is not that it's a global variable, it's other structure inherent to how the program is designed. And examples like

    unique_ptr<Foo> p;
    void bar() {
      p = make_unique<Foo>(...);
      Foo xyz(*p); // Forbidden! (NOT)
    }
are easily dealt with.


And yet, most of the Unix tools just work, which I cannot say of the average C++ program.

Can you name one C++ program with the same track record as postfix or qmail?


qmail contained a buffer overflow that allowed remote root access, prevented only by having a low limit on available memory.

http://web.archive.org/web/20160401021400/http://www.guninsk...

djb is a smart and motivated guy and even he isn't quite capable of writing correct C code; how much chance does anyone else have?


qmail is supposed to be used with "softlimit" from daemontools. All decent tutorials mention that.

Again, which C++ program has been even analyzed to that level?


Did it ever occur to you that if a C++ programmer of security mindset wanted to make something like postfix or qmail, they'd just pick a language that was garbage collected?


No, it did not occur to me. The reason is precisely that I have not seen this mindset among C++ programmers, at least in the open source world. Llvm is an exception.

What I do see is hubris. They are overconfident and pretend that one can use C++ like Haskell, i.e. one can safely go ahead and pile one abstraction onto another.

C programmers at least know the limitations of their language.

This is the just impression that I and many others get. I'm sure there are counterexamples.


C programmer here; I don't like C++ much because every library tends to reinvent the whole stack (or maybe that's just my impression from using MFC and Qt, which are notorious in that regard).

RAII and scope based construction/destruction is actually quite nice.


At least C++ has a stack to re-invent. With C you're missing Vector and Map before you even start.


> every library tends to reinvent the whole stack

This is a problem with vocabulary types. C tends to do a good job of providing them. C++ tends not to, though there has been some progress in the standard lately in defining classes like unique_ptr and string_view.

C tends to fall down by providing raw memory (an int and a char) to represent fairly subtle relationships (the int represents the size of the string pointed to by the char... what happens when the int is negative?). So C libraries don't 'reinvent the whole stack', but this sort of bookkeeping logic tends to cause bugs (at least security bugs).


Check out the Juce library. It's a fantastic lib, primarily intended for audio programming but I've found the GUI tools to be very nice.


The more I learn C++, the more I can't stand it. Its ever-increasing complexity keeps being a distraction from the actual problem you're trying to solve.


I've reached peak loathing of C++ at this point. I can see why people would want to lock up the recursive template metaprogramming double barrled automatic shotgun.

...but I get confused by the people who do that, and then turn around to embrace C, the circus where people juggling the chainsaws of "faking templates with macros", "terrible build times", "yet more security vulnerabilities resulting from undefined behavior", "no C-standard defined threading / async support until C1X", and more.

It's common, but I don't get it.


Well, people like C because it contains less language features and therefore is simpler. The error is thinking that simple and easy are the same thing. C is fiendishly difficult to write correctly.


Yes, but the distractions of C are just as inconvenient, though wholly different. The amount of extra low-level work you must do in a C program considerably adds to the time you need to make it work, time that could be spent thinking about more interesting things. I'd rather reach for a C++ vector and just let it manage the memory for me (or unique_ptr or many other easy tools) while I'm crafting my program logic then spend a nontrivial fraction of my time making sure my memory handling is correct.


But the same can be said about mathematics, for example. While true, such statement is only saying something about the process of learning; it does not mean that C++ (or mathematics) is badly designed, useless, or is not worth learning. When you are just learning, of course you tend to be more focused on the subtleties of the subject itself rather than on its applications. Which is not to say that it is a good idea: it is through application that you begin to understand and fully appreciate the subject.


Among many reasons, I would choose C++ over those languages because it has operator overloading. Expressing 3D spatial math and linear algebra in a language without operator overloading is incredibly tedious.


Coming from C++ and iOS world I would say recent C++ has evolved a lot. And I think its better than Objective-C.


Have you tried modern C++ (that is, C++11 and C++14)? It has a very different feel these days. I used to prefer C as well, but C++ has come a long way in ease of usability.


It all depends on what you are building. Objective C is absolutely fantastic for rapidly developing GUIs because of its dynamic runtime and flexible typing. On the other hand, C++ makes it a lot easier to write efficient, (relatively) safe data structures.

I find it extremely frustrating when I have to use either language in an area that it wasn't designed for.


What has always bugged me about C++, but maybe I am overlooking some reason why this generally cannot work, is that it does not resolve circular dependencies such as this one:

    // file: A.h
    class A {
      B* _b;
    };
    
    // file: B.h
    class B {
      A* _a;
    };
I mean a pointer has a fixed size, so couldn't the compiler just leave some sort of type placeholder in class A until class B is eventually defined.


Structure layout isn't the hard part, it's having the rest of the language cope with the ambiguity of everything.

  // Things.h
  namespace Foo { class A { C* c; }; }
  namespace Bar { class B { C* c; }; }
Are A::c and B::c the same type?

  // TU1.cpp
  class C {};
Were you right?

  // TU2.cpp
  namespace Foo { class C {}; }
  namespace Bar { class C {}; }
What about now?

  // TU3.cpp
  class C {};
  namespace Foo { class C {}; }
  namespace Bar { class C {}; }
This may get you shot, but is legal. What about now?

Ahh - all three are in the same program. Just pretend they #included <c.h>, <foo/c.h>, and <bar/c.h>. Having A::c's type be different in different contexts is a violation of the "One Definition Rule", for which the punishment is undefined behavior.

Although you can still recreate the above scenarios by just #including <Things.h> after <c.h> and company, but you're a bit more likely to have Things.h #include what it needs to clarify the situation. Hopefully. So maybe we can get away with it!

  void foo() { D * d; }
Is that a pointer definition or invoking operator* against two globals?

  void bar() { E < 42 > e; }
Now you're just fucking with me. Is that some comparison operators or a template?

  void baz() { F < (42) > f; }
That didn't clarify anything. Stop it.

  void he_comes() { G < (42) > g(); }
What do you mean I just declared a function? Stop it!

  v̹̊̈́̇ͦ̌ͭ͂ͅo̅̉̀ͪ̚ī̵̠̘̋d̸͚̪̝̹͙͆͛̿ ͉̜̳ͪͬ́ͣ̌ẑ̸̹̹̩̩a̡̰ͣͭ̄͒ͫ̇̚l̛̹̫͓̣͖͈̐ͬg̢͕̘͙o̪͆̏ͥ͟(̖̭̟̱̰ͩ)̘̬͉̺̉̓ ̦͆́̚{ ̶̪͙̹͈ͮͧ̋ͪͤͅĜ͔̙̉͢ ̝̯ͭ̈́͘<̨͊̂͋̎ ̤͇̼̝͔͇ͯ̑̋ͫ̾͐̚(ͨͪͦ̈̈́ͥ4͍͗̆2̲̞̦̳̖͡)͖͇̟̥ͮ͘ ̺̖͙̘̳͛̐̊͊>̼͍̘̦͖̎ ̧̘̻̭͉̺g̟͓̑ͦ̌̃(̪ͬͪͤ̎̉͘ȉ̮̥͖́n̸̝̪̗̯͇̓̈́̀̔̄̍́t͓̟͍ͨͪ(̰͈̻͎̣͉̀̿̈͑)̝͚̆̃̇)̠̃;̺͎̙̰̈́̏̚͘ ̶̜͍̠̼ͬ}̡̤̰̮̳͈̌͛ͤ̈́
A function accepting a function pointer? Stooooop!


This can be done, check the end of this answer: http://stackoverflow.com/a/628079/429972

   // file: A.h
   class B;
   class A {
     B* _b; // or any of the other variants.
   };


You can also do this

    class A {
      class B* _b;
    };


It can easily be done by declaring a prototype of B:

  // file: A.h
  class B;

  class A {
    B* _b;
  };

  // file: B.h
  class B {
    A* _a;
  };


You can just do

    class B;
    class A {
      B* _b;
    };
and it'll be happy.


Yep the beauty (and maybe curse) of C++ iterators is that they are a faithful abstraction of pointers.


some people would argue that pointers themselves are a type of iterator


I would argu those people are terribly wrong, as pointers serve more purposes than iteration


My most important C++ Aha moment was in 1995 when I wrote my first lines of Java and realized that it was possible to write OO code without having to fight the compiler every step of the way.


My Aha moment was in 2008 when I saw a java program consume 400+mb of RAM for a text editor. Then if you clicked on too many things, it crashed under its own weight (eclipse).

"But it has garbage collection" they said. "You dont have to worry about pesky memory managment, the language has an intelligent VM that handles it for you!".


Does this reflect your view regarding development in C++ and Java and is your comment merely a lazy reflection of that?


That was 2006. Myer's most impressive "Aha moment" w.r.t. C++ came last December, eclipsing the others:

http://scottmeyers.blogspot.ca/2015/12/good-to-go.html




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: