Live data from Hacker News

My Most Important C++ Aha Moments (2006)

artima.com

81–84 of 84 posts

Re: My Most Important C++ Aha Moments (2006)

#81

Earlier quoted context omitted.

> 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 dis…

> 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.

Re: My Most Important C++ Aha Moments (2006)

#82

> 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 defau…

> 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.

Re: My Most Important C++ Aha Moments (2006)

#83

Earlier quoted context omitted.

> 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 dis…

> 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 di…

> 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 p;
    void bar() {
      p = make_unique(...);
      Foo xyz(*p); // Forbidden! (NOT)
    }
are easily dealt with.

Re: My Most Important C++ Aha Moments (2006)

#84

> 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 cou…

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.
Post reply on HN