|
|
Subscribe / Log in / New account

GCC begins move to C++

From:  Mark Mitchell <mark-AT-codesourcery.com>
To:  GCC <gcc-AT-gcc.gnu.org>
Subject:  Using C++ in GCC is OK
Date:  Sun, 30 May 2010 17:26:16 -0700

I am pleased to report that the GCC Steering Committee and the FSF have
approved the use of C++ in GCC itself.  Of course, there's no reason for
us to use C++ features just because we can.  The goal is a better
compiler for users, not a C++ code base for its own sake.

Before we start to actually use C++, we need to determine a set of
coding standards that will apply to use of C++ within GCC.  At first, I
believe that we should keep the set of C++ features permitted small, in
part so that GCC developers not familiar with C++ are not rapidly
overwhelmed by a major change in the implementation language for the
compiler itself.  We can always use more of C++ later if it seems
appropriate to do so, then.

For example, I think it goes without question that at this point we are
limiting ourselves to C++98 (plus "long long" so that we have a 64-bit
integer type); C++0x features should not be used.  Using multiple
inheritance, templates (other than when using the C++ standard library,
e.g. std::list<X>), or exceptions also seems overly aggressive to me.
We should use features that are relatively easy for C programmers to
understand and relatively hard for new C++ programmers to misuse.  (For
example, I think constructors and destructors are pretty easy and hard
to misuse.)

Because C++ is a big language, I think we should try to enumerate what
is OK, rather than what is not OK.  But, at the same time, I don't think
we should try to get overly legalistic about exactly what is in and what
is out.  We need information guidelines, not an ISO standard.

Is there anyone who would like to volunteer to develop the C++ coding
standards?  I think that this could be done as a Wiki page.  (If nobody
volunteers, I will volunteer myself.)  Whoever ends up doing this, I
would urge the rest of us not to spend too much time in the C++
coding-standards bikeshed; we're not going to win or lose too much
because we do or do not permit default parameters.

-- 
Mark Mitchell
CodeSourcery
[email protected]
(650) 331-3385 x713




to post comments

GCC begins move to C++

Posted May 31, 2010 17:42 UTC (Mon) by gregj (guest, #16266) [Link] (22 responses)

llvm effect ?

GCC begins move to C++

Posted May 31, 2010 19:22 UTC (Mon) by Ed_L. (guest, #24287) [Link] (21 responses)

Possibly. But in the sense that, as a long-time C++ programmer, were I to choose to volunteer my time between a C project and a (very) roughly equivalent C++ project, all else being roughly equal, I'd choose C++. Its not that I can't code in C, or can't maintain and moderately enhance existing C code, its just that I've been thinking OO for so long that for any new project and most additions to old, the first thing I think about (after defining purpose and goals) is how to encapsulate data and methods. Anymore restricting myself to C just seems too unnecessarily hard.

You do want to keep it simple though, and I'm not certain I'd agree with Mark's contention its hard to misuse C++ constructors and destructors. He may have difficulty, but I've always seemed to manage just fine...

GCC begins move to C++

Posted May 31, 2010 19:28 UTC (Mon) by johill (subscriber, #25196) [Link]

For example, I think constructors and destructors are pretty easy and hard to misuse.

I had to read that twice too, especially in the context, but I think he's agreeing with you :)

GCC begins move to C++

Posted May 31, 2010 20:44 UTC (Mon) by stijn (subscriber, #570) [Link] (19 responses)

Data encapsulation is done quite easily in C. It is better supported in the C++ language itself (although one person's gain is another person's pain), but I always thought it to be one of the quite minor differences between the two languages. That is, if you do not need inheritance, templates, or operator overloading, you get something very close to C. So I wonder, is this overly simplistic, or is gcc on course for using one of the aforementioned three.

GCC begins move to C++

Posted May 31, 2010 21:27 UTC (Mon) by stevenb (guest, #11536) [Link] (5 responses)

Remains to be seen. My guess:

Inheritance? Likely.
Templates? Only for basic data structures from e.g. STL.
Operator overloading? Unlikely.

But that is what the main debate will be about: What code style and constructs should be used, and what should be avoided. I guess it will take some time to come to some kind of agreement...

GCC begins move to C++

Posted Jun 1, 2010 0:50 UTC (Tue) by Ed_L. (guest, #24287) [Link]

I'm not involved with GCC and its been many years since I've even looked at their code. Still, its not inconceivable they might find some utility in typename templates. But its a slippery slope. GCC enlists some of the most practised C coders in the world, They obviously want to keep their C++ guidelines simple enough not to alienate productive members who simply Refuse To Go There.

Bergeson's Second Law of Circuit Design:

"Keep it simple. If you cannot explain how your circuit works to a retarded chimpanzee, you will probably have difficulty explaining why, in fact, it does not work at all."

GCC begins move to C++

Posted Jun 1, 2010 4:58 UTC (Tue) by jgg (subscriber, #55211) [Link] (2 responses)

It will be interesting to see how this goes for them..

When people start talking about limiting complex C++ features for whatever reason (too easy to abuse! too complex! etc) I generally point out the C++ standard library string class as a pretty stand out example of why this is a bad idea.

The class has an easy user interface, makes use of a great amount of C++ magic and is *dramatically* better than the C equivalence.

A good example for gcc might be the uint64_t type - to support certain build situations uint64_t is used sparingly. But you can make a completely transparent and code compatible uint64_t using a C++ class and a wack of operator overloading. How is this stuff like this not a good idea??

I was reading the thread and IMHO, people are focusing on the wrong thing. The best way to use C++ is to continue to micro optimize your fast/common cases but use fancy C++-ness to 'correctness' optimize the boring uncommon, infrequent, error, and unlikely cases.

GCC begins move to C++

Posted Jun 1, 2010 9:50 UTC (Tue) by mjthayer (guest, #39183) [Link]

> When people start talking about limiting complex C++ features for whatever reason (too easy to abuse! too complex! etc) I generally point out the C++ standard library string class as a pretty stand out example of why this is a bad idea.
> The class has an easy user interface, makes use of a great amount of C++ magic and is *dramatically* better than the C equivalence.

I'm certainly no C++ guru, but I always feel that complex C++ features are a very slippery slope. Specifically, that you will always find situations in which any given feature makes a great deal of sense, but that you will also find many many more in which it seems to make sense but actually does more harm than good, in wasted development time, tricky bugs and hard-to-follow code. As long as they are only used by people qualified to write standard libraries and their ilk, all is well, but when common mortals (like me!) start to use them things can degenerate very fast.

GCC begins move to C++

Posted Jun 5, 2010 15:48 UTC (Sat) by vonbrand (guest, #4458) [Link]

The problem with stuff like C++'s strings or the proposed uint64_t use of "magic" is that as long as the magic doesn't leak out (i.e., the result is usable by mere mortals) everything is fine. As soon as the would-be magician looks into the book, caos as in the Disney classic Magician's Apprentice ensues.

You need magicians to write the magic code, and disciplined programmers who know that they shouldn't try their hand at magic... the last part is next to hopeless.

GCC begins move to C++

Posted Jun 1, 2010 18:49 UTC (Tue) by sustrik (guest, #62161) [Link]

Virtual functions. It's such an annoyance to manage virtual function pointers tables in C by hand.

GCC begins move to C++

Posted Jun 1, 2010 21:54 UTC (Tue) by man_ls (guest, #15091) [Link] (12 responses)

Data encapsulation is done quite easily in C.
Of course data encapsulation is easy in C, but not data and methods encapsulation. That is the whole point of objects: define your structures and the code that manages them close together. In turn, each piece of data inside your structures may have associated methods to manipulate it.

It is possible to do something like behavior encapsulation in C, with structures of pointers, but they are cumbersome and require too much plumbing. Maybe this is what you think is easily done in C.

Inheritance is the icing on the cake, since it allows specific behavior depending on the kind of data. I guess you could do something like it with C unions, but it would quickly lead to madness.

GCC begins move to C++

Posted Jun 1, 2010 23:54 UTC (Tue) by anselm (subscriber, #2796) [Link] (5 responses)

C++ started out as a glorified pre-processor for C, so it was originally about trying to contain the madness one would otherwise have to deal with when programming the same sort of thing in plain C.

This also proves that nothing the original C++ did (which includes most of the object orientation, inheritance, ...) was in fact impossible to do in plain C, since the C++ code had to be translated to plain C for the C compiler -- it was just messy as hell. I'm not a serious C++ programmer but I'm told that present-day C++ contains stuff that can no longer be feasibly translated to C.

GCC begins move to C++

Posted Jun 2, 2010 10:36 UTC (Wed) by ncm (guest, #165) [Link] (4 responses)

C++ was never at any point in its history any sort of pre-processor for C. In its very first incarnation, it was a full compiler, in every sense, that fully parsed and analyzed its input, and then emitted code to implement the semantics required. That the object language chosen was C, which is/was a subset of of C++, does not confuse the careful thinker.

It appears that C++ was the first language first implemented in this way, but most languages that came after were done the same way, for the same reasons.

GCC begins move to C++

Posted Jun 2, 2010 11:18 UTC (Wed) by anselm (subscriber, #2796) [Link] (2 responses)

Whatever. The point is that the OOP things C++ does aren't impossible to do in plain C (see AT&T's 1980s-vintage cfront); they're on the whole just too much of a hassle to write down and keep track of in plain C, which doesn't mean that people won't try anyway (for interesting subsets of the functionality), nor does it mean that if they do they won't succeed.

GCC begins move to C++

Posted Jun 2, 2010 17:48 UTC (Wed) by cry_regarder (subscriber, #50545) [Link] (1 responses)

Whatever. Your argument is equivalent to saying that generic programming and functional programming are facilitated by assembly language.

GCC begins move to C++

Posted Jun 2, 2010 22:26 UTC (Wed) by anselm (subscriber, #2796) [Link]

You can probably do functional or generic programming in assembly language, only (similar to the C++ vs. plain C situation) it is enough of a hassle that, on the whole, people don't bother these days because more convenient methods are available.

GCC begins move to C++

Posted Jun 3, 2010 10:17 UTC (Thu) by HelloWorld (guest, #56129) [Link]

C++ was never at any point in its history any sort of pre-processor for C.
It depends on whether you consider "C with Classes" to be part of the history of C++. Stroustrup writes in hos book "Design and Evolution of C++" (page 27):
In October1979 I had a running preprocessor, called Cpre, which added Simula-like classes to C
However, that thing was replaced by Cfront not much later.

GCC begins move to C++

Posted Jun 2, 2010 9:38 UTC (Wed) by stijn (subscriber, #570) [Link] (5 responses)

Of course data encapsulation is easy in C, but not data and methods encapsulation. That is the whole point of objects: define your structures and the code that manages them close together.

It depends on your expectations I think. I'll say that defining your structures and the code that manages them close together is entirely possible in C, and in fact the prefered way. Not in the way that C++ does it, but taken on its own the statement applies to C. I am refering to bog-standard C by the way, nothing that tries to stuff each and every structure full of method pointers and/or uses a shower of preprocessing magic.

GCC begins move to C++

Posted Jun 2, 2010 10:07 UTC (Wed) by dark (guest, #8483) [Link] (4 responses)

That's a good point, and you've put your finger on something that has been bothering me about C++. You see, C is actually better at this than C++. In C, I can declare a structure and its methods in a header file, and then define them close together in an implementation file. But when making a C++ class, I have to define the structure and the methods in separate places: the structure fields (= class members) in a header file and the methods in an implementation file. Unless I just put everything in the header file, but that has its drawbacks.

GCC begins move to C++

Posted Jun 2, 2010 13:24 UTC (Wed) by dgm (subscriber, #49227) [Link]

I had to read your comment three times before understanding (hopefully) what you meant: opaque data types, also known as incomplete types.

GCC begins move to C++

Posted Jun 2, 2010 14:32 UTC (Wed) by mjthayer (guest, #39183) [Link]

> But when making a C++ class, I have to define the structure and the methods in separate places: the structure fields (= class members) in a header file and the methods in an implementation file.

Or you define a virtual parent class with just the public interface in a header file and the actual implementation (including its non-public members) in the implementation file. There is a negligeable price in efficiency of course, which should nearly always be worth paying.

GCC begins move to C++

Posted Jun 2, 2010 18:05 UTC (Wed) by jwakely (subscriber, #60262) [Link]

You can do exactly the same in C++, since it's almost a strict superset, how is C better at it?
There are also other alternatives in C++, with their own pros and cons.

Java advantage

Posted Jun 2, 2010 19:37 UTC (Wed) by man_ls (guest, #15091) [Link]

In this respect Java is definitely better than both: you define attributes and methods on the same file, and you don't need separate declarations.

It has always bothered me that I needed to add something in two places to get an accessible function. With a little perspective header files look like a way to make the preprocessor happier at the cost of developer time. Why C++ didn't fix this little detail is beyond me.

GCC begins move to C++

Posted May 31, 2010 19:35 UTC (Mon) by ikm (guest, #493) [Link] (12 responses)

Ok, Linus is next ;)

GCC begins move to C++

Posted May 31, 2010 21:55 UTC (Mon) by Lovechild (guest, #3592) [Link] (11 responses)

http://thread.gmane.org/gmane.comp.version-control.git/57...

I believe it might be a sign of the apocalypse, but what the hell, it sounds like a fun ride. Roll the movie.

GCC begins move to C++

Posted May 31, 2010 23:17 UTC (Mon) by HelloWorld (guest, #56129) [Link] (10 responses)

I was going to write that someone has written a long rebuttal to that email, but then I realised that there isn't really a lot to rebut, given that Torvalds hardly makes any real points at all. Anyway, here's what it reminded me of:
http://warp.povusers.org/OpenLetters/ResponseToTorvalds.html

GCC begins move to C++

Posted Jun 1, 2010 3:27 UTC (Tue) by jrn (subscriber, #64214) [Link] (5 responses)

I don’t think Linus’s messages were meant to do much more than explain why core Git should stay in C, so the rebuttal seems a bit misdirected. But I could not resist mentioning a different problem: the mistake of not taking into account differences in idiom, which seems to be well described here: http://www2.research.att.com/~bs/bs_faq.html#compare

One of the merits of idiomatic C is that it is easy to guess roughly what is going to happen on the machine due to your code. I cannot imagine an experienced C programmer using

for (i = 0; i < strlen(line); i++)

except to prove a point, for exactly this reason.

Especially amusing was this:

> Unless countless very respected computer scientists and programmers around the world are completely wrong, we just cannot deny that non-abstract, low-level code tends to be quite unmaintainable. This is the more true the larger the program is.

> Torvalds not only admits here that C, by its very nature, leads you to create non-abstract, low-level code, that C gives you the mentality required to create such non-abstract code, but he actually claims that it's a good thing that this is so.

How does one explain the Linux kernel? Of course it includes abstraction where appropriate, just like most reasonably-sized C programs do.

GCC begins move to C++

Posted Jun 2, 2010 18:35 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (4 responses)

"I cannot imagine an experienced C programmer using
for (i = 0; i < strlen(line); i++)
except to prove a point, for exactly this reason."

Nope, it's a great example. Because C programmers routinely use examples which no sane C++ developer will ever write to prove that C++ is BAD BAD BAD. Why shouldn't C++ developers do the same?

"How does one explain the Linux kernel? Of course it includes abstraction where appropriate, just like most reasonably-sized C programs do."

Linux is good because it's not actually quite that big. Most of code is in the drivers. And its infrastructure code if fairly compact and has quite clean abstractions.

GCC begins move to C++

Posted Jun 2, 2010 18:45 UTC (Wed) by ikm (guest, #493) [Link] (1 responses)

> Why shouldn't C++ developers do the same?

Maybe because they don't have a problem with C?

GCC begins move to C++

Posted Jun 2, 2010 18:47 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link]

That's a good point :)

GCC begins move to C++

Posted Jun 4, 2010 9:53 UTC (Fri) by jrn (subscriber, #64214) [Link] (1 responses)

> Because C programmers routinely use examples which no sane C++ developer will ever write to prove that C++ is BAD BAD BAD.

Good point. I get annoyed when C programmers do it, too. :)

GCC begins move to C++

Posted Jun 4, 2010 10:06 UTC (Fri) by dlang (guest, #313) [Link]

the problem is that there are so many insane developers around (in every language, I'm not picking on C++ here :)

GCC begins move to C++

Posted Jun 1, 2010 3:54 UTC (Tue) by jamesh (guest, #1159) [Link] (1 responses)

Whether you agree with Linus's opinion on C++, there is a pretty big difference between the gcc and git situations.

For git, a minor contributor was proposing changes that would negatively affect the core developers' ability to maintain the code base (since C++ is not one of their areas of expertise). In the gcc case, it is the core developers themselves who are suggesting the change and believe it will make maintenance easier.

GCC begins move to C++

Posted Jun 1, 2010 6:30 UTC (Tue) by adobriyan (subscriber, #30858) [Link]

> For git, a minor contributor was proposing changes

Proposing? I don't think so.

> When I first looked at Git source code two things struck me as odd:
> 1. Pure C as opposed to C++. No idea why. Please don't talk about
> portability, it's BS.
> 2. Brute-force, direct string manipulation. It's both verbose and
> error-prone. This makes it hard to follow high-level code logic.

GCC begins move to C++

Posted Jun 1, 2010 9:08 UTC (Tue) by ctg (guest, #3459) [Link] (1 responses)

Linus seems to be saying two things:

1) Real Programmers use C
2) C++ is about architecture, as much as code, and the architecture tends to be rigid. With C, you can ignore/change/adapt architectures within a project/timeframe.

While I have some sympathy with the statements, I think it is largely a question of taste.

GCC begins move to C++

Posted Jun 1, 2010 9:22 UTC (Tue) by ikm (guest, #493) [Link]

C++ allows creating high-level abstractions, something that C doesn't really allow. The problem with abstractions, of course, is that programmers aren't required to understand their underlying complexities. However, just because the complexity is abstracted away from the programmer, it doesn't go anywhere -- it is just being ignored by the programmer. Therefore C++ programmers tend to create less efficient code than C ones.

Note the word 'tend'. This problem is not with the language, but with the monkey coders. How many C++ programmers actually understand how virtual functions are implemented, for instance?

With GCC, the answer would actually be "all of them" :)

The Linus' solution is just not to use C++ and do all the dirty work the compiler would've done for you yourself. I like the GCC's one -- to limit the allowed subset and to be careful and thoughtful -- MUCH better.

GCC begins move to C++

Posted Jun 1, 2010 3:22 UTC (Tue) by jrn (subscriber, #64214) [Link] (43 responses)

If gold is an indication of the kind of code to expect (no exceptions, no deeply nested blocks, not too many expensive destructors), I am happy to see GCC start using C++. This should be interesting to watch.

GCC begins move to C++

Posted Jun 1, 2010 8:33 UTC (Tue) by zorro (subscriber, #45643) [Link] (42 responses)

What's wrong with using exceptions?

GCC begins move to C++

Posted Jun 1, 2010 10:50 UTC (Tue) by nix (subscriber, #2304) [Link] (3 responses)

If you use them widely, exception-safety becomes an absolute nightmare unless you are completely rigorous about using RTTI for everything *and* none of your destructors can ever fail. If you use RTTI at all, you'd better use exceptions rather than, say, longjmp()...

GCC begins move to C++

Posted Jun 1, 2010 11:52 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link]

Failing destructors are NOT GOOD in any case. If your destructor can fail in a 'normal' situation - it shouldn't be a destructor.

GCC begins move to C++

Posted Jun 1, 2010 17:22 UTC (Tue) by jwakely (subscriber, #60262) [Link] (1 responses)

RTTI? RAII shurely?

GCC begins move to C++

Posted Jun 2, 2010 11:38 UTC (Wed) by nix (subscriber, #2304) [Link]

Yes, of course. I always mix those acronyms up: I think it's the letter-doubling that does it. I even mix them up if I expand them in my head right before.

GCC begins move to C++

Posted Jun 1, 2010 11:57 UTC (Tue) by mjthayer (guest, #39183) [Link] (19 responses)

> What's wrong with using exceptions?

This is an area where I still have a lot to learn, but my answer at this stage is that exceptions allow you to move error handling away from the place where the error occurs. The idea is that handling it somewhere else may make more sense, for instance because stretches of code littered with error handling can be harder to read. If methods called in a stretch of code throw exceptions instead of using return values to signal more-or-less fatal errors then you can enclose the entire stretch in a "try" block and deal with all the exceptions that might occur in a single "catch" block after the "try" block.

The main problem here is that the further away you get from the source of the error, the harder it becomes to deal with it properly - you have to distinguish what all the different exceptions you might catch actually mean and know what to do with each, and as you move your exception handling further away from the source, you end up potentially catching exceptions from more and more different places in one catch block, and being further and further away from the bits you need to clean up. Which in theory should be helped because of destructors getting called along the exception path, but this requires a lot of dilligence (especially on a project with lots of developers) to assure.

On the other hand, the chances of a random more-or-less-fatal error being handled correctly are pretty low in almost any project under most circumstances due to the amount of testing needed to achieve this.

GCC begins move to C++

Posted Jun 1, 2010 13:02 UTC (Tue) by avik (guest, #704) [Link] (18 responses)

Most error handling code does not actually handle the error, it merely undoes the effects of the code prior to the error and then propagates said error to the caller. Exceptions allow you to avoid this error cleanup and propagation code completely. Example:
    int f(struct foo *f)
    {
        int r;
        struct bar *b;

        b = kmalloc(GFP_KERNEL, sizeof(*b));
        if (!b)
            return -ENOMEM;
        spin_lock(&f->lock);
        r = g(f);
        if (r)
             goto out_free;
        f->bar = b;
        spin_unlock(&f->lock);
        return 0;
    out_free:
        spin_unlock(&f->lock);
        kfree(b);
        return r;
    }
becomes:
    void f(foo *f)
    {
        auto_ptr<bar> b(new(GFP_KERNEL) bar);

        spinlock_t::guard lock_guard(f->lock);
        g(f);
        f->bar = b.release();
    }
Unlocking and freeing on error are automatic, and the function body size is greatly reduced.

GCC begins move to C++

Posted Jun 1, 2010 13:56 UTC (Tue) by rev (guest, #15082) [Link]

Exactly. Handing errors using error codes en returning these up the call tree untill the error code is dealt with amounts to unraveling the stack manually. A thing the compiler generated code does for you automatically when you use exceptions.

GCC begins move to C++

Posted Jun 1, 2010 16:10 UTC (Tue) by mjthayer (guest, #39183) [Link] (8 responses)

> Most error handling code does not actually handle the error, it merely undoes the effects of the code prior to the error and then propagates said error to the caller. Exceptions allow you to avoid this error cleanup and propagation code completely. Example:

Things tend to get a bit messier though when you have to call non-exception-aware code, which in practice happens as soon as you start dealing with external APIs. One can write class wrappers around them of course, but that is only moving the issue slightly (it is easier in Java, where you get those wrappers as part of the environment!) Not to mention if you want to ensure that your object can survive an exception and remain in a sane state, although I admit that wanting that may be a sign that you are doing something wrong.

GCC begins move to C++

Posted Jun 1, 2010 16:23 UTC (Tue) by avik (guest, #704) [Link] (7 responses)

Things tend to get a bit messier though when you have to call non-exception-aware code, which in practice happens as soon as you start dealing with external APIs. One can write class wrappers around them of course, but that is only moving the issue slightly
It's exactly the right solution to the problem. If, for example, you deal with file descriptors, you write a wrapper class that calls close() in its destructor.
Not to mention if you want to ensure that your object can survive an exception and remain in a sane state, although I admit that wanting that may be a sign that you are doing something wrong.
It's perfectly reasonable and doable to roll back to a sane state. It's pretty easy if you use RAII extensively.

GCC begins move to C++

Posted Jun 1, 2010 16:30 UTC (Tue) by mjthayer (guest, #39183) [Link] (4 responses)

> It's perfectly reasonable and doable to roll back to a sane state. It's pretty easy if you use RAII extensively.

A random example of what seems tricky to me: you are creating some container class and have a method to store several elements into the container. An exception occurs half way through. It would be nice to roll back to the state you were in before the method was called, which is certainly doable, but not quite trivial either.

GCC begins move to C++

Posted Jun 1, 2010 16:45 UTC (Tue) by avik (guest, #704) [Link] (2 responses)

Good example. You could implement this as follows (pseudocode):
void container::splice(container& other)
{
   container::undo u(*this);
   for (i in other)
        u.append(this->append(i));
   u.clear();
}

class container::undo {
public:
    undo(container& c) : _c(c) {}
    ~undo() {
        for (i in undo_list backwards)
            _c.remove(i);
    };
    void clear() { undo_list.clear(); }
private:
    container& _c;
    list undo_list;
};

so, while adding items you also log them in an undo list, which is used to undo all that's been done on an exception.

GCC begins move to C++

Posted Jun 1, 2010 18:45 UTC (Tue) by mjthayer (guest, #39183) [Link] (1 responses)

> Good example. You could implement this as follows (pseudocode):

Certainly. I think I have even implemented this before, but for me it is not trivial in the sense that I would need to run tests on it to be confident that it worked as intended.

If one takes the point of view that exceptions should only be used for non-recoverable errors of course then this ceases to be relevant, and it is enough to ensure that the object can be cleanly destroyed, so no need to ensure a consistant state otherwise. I suspect that if the STL designers had taken this point of view, C++ would be used rather differently today.

GCC begins move to C++

Posted Jun 2, 2010 9:57 UTC (Wed) by jwakely (subscriber, #60262) [Link]

> If one takes the point of view that exceptions should only be used for non-recoverable errors of course then this ceases to be relevant, and it is enough to ensure that the object can be cleanly destroyed, so no need to ensure a consistant state otherwise.

That point of view is unsuitable, impractical or unnecessary in a lot of code, especially in standard library components. The reasons why have been known for more than ten years,
http://www.boost.org/community/exception_safety.html

> I suspect that if the STL designers had taken this point of view, C++ would be used rather differently today.

Yes, it would be used less.

GCC begins move to C++

Posted Jun 2, 2010 14:50 UTC (Wed) by endecotp (guest, #36428) [Link]

> you are creating some container class and have a method to
> store several elements into the container. An exception occurs
> half way through. It would be nice to roll back to the state
> you were in before the method was called

If your container is a linked list like std::list, you do it something like this (pseudo-code):

list tmp_list;
for (.....) {
tmp_list.push_back(....);
}
main_list.splice(main_list.end(),tmp_list);

list::splice cannot normally throw, so to addition of the new items to the main list is atomic. If an exception occurs during the loop, the temporary list is destroyed during unwinding and the main list is never touched. splice() just involves a handful of pointer swaps so there is no efficiency issue with this code.

If your container is more like a std::vector it's a bit more verbose; I would write something like this (pseduo-code):

size_t old_size = main_list.size();
try {
for (......) {
main_list.push_back(.....);
}
}
catch (...) {
main_list.resize(old_size);
throw;
}

You could wrap that up into something more transactional-looking if you wanted (pseudo-code):

{
transaction t(main_list);
for (......) {
main_list.push_back(....);
}
t.commit();
}

where transaction's ctor saves the old size and the dtor calls resize() unless commit() has been called.

> not quite trivial either

No it's not quite trivial, but I would hope that anyone who has got their "C++ programmer's license" would be able to do it.

Phil.

GCC begins move to C++

Posted Jun 2, 2010 16:40 UTC (Wed) by HelloWorld (guest, #56129) [Link] (1 responses)

It's exactly the right solution to the problem. If, for example, you deal with file descriptors, you write a wrapper class that calls close() in its destructor.
The close system call can fail, therefore its return value must be checked. Now what do we do if we called close() from a destructor and it failed? We cannot throw an exception, because if another exception is flying, this cause the enviroment to call terminate(). So we can't really do anything except ignoring the error, which is also not acceptable. The consequence is that you must not close file descriptors in destructors.

GCC begins move to C++

Posted Jun 2, 2010 17:25 UTC (Wed) by avik (guest, #704) [Link]

Typically you provide a close() method that does a ::close() and throws an exception on failure. The close() in the destructor only frees the file descriptor. Some information is lost (the second error), but that's not different to C where you typically have a single-valued error return (and must also close() the fd).

Better Than Exceptions

Posted Jun 2, 2010 11:03 UTC (Wed) by ldo (guest, #40946) [Link] (7 responses)

avik wrote:

Exceptions allow you to avoid this error cleanup and propagation code completely.

Actually, it can be done more elegantly without exceptions. Here’s a rewrite of your example:

int f
  (
    struct foo * f
  )
  {
    int r = 0;
    struct bar * b = NULL;
    do /*once*/
      {
        b = kmalloc(GFP_KERNEL, sizeof *b);
        if (b == NULL)
          {
            r = -ENOMEM;
            break;
          } /*if*/
        spin_lock(&f->lock);
        do /*once*/
          {
            r = g(f);
            if (r != 0)
                break;
            f->bar = b;
            b = NULL; /* so I don't free it yet */
          }
        while (false);
        spin_unlock(&f->lock);
      }
    while (false);
    kfree(b);
    return r;
  } /*f*/

No gotos, and much easier to verify that everything will always be cleaned up no matter what path is taken, don’t you think?

Better Than Exceptions

Posted Jun 2, 2010 11:35 UTC (Wed) by avik (guest, #704) [Link] (1 responses)

Sorry, your code is completely unreadable. How can you even compare it to the 4 lines of code in my example?

Better Than Exceptions

Posted Jun 2, 2010 13:01 UTC (Wed) by mpr22 (subscriber, #60784) [Link]

I find it readable but gratuitously verbose... but then, I have recently been up to my armpits in the Nethack source code stripping out its support for pre-ANSI compilers and steam-driven System III / Version 7 Unices.

Better Than Exceptions

Posted Jun 2, 2010 12:11 UTC (Wed) by farnz (subscriber, #17727) [Link]

Actually, I much prefer avik's example to yours. As a competent C++ programmer who also knows C, I recognise the exception safety guarantee of auto_ptr, and I can guess that spinlock_t::guard provides the strong exception safety guarantee (an exception thrown that causes it to be destroyed returns it to the original state). I also know that the release method on auto_ptr returns the contained pointer and stops auto_ptr from deleting it.

As a result of this general C++ knowledge, and the assumption that the programmer hasn't been writing C in C++ (or otherwise behaved like a crack-addled monkey), I can see very quickly in avik's C++ example that nothing can leak. I can also see clearly what the code is meant to do.

In your version, I spend most of my mental energy figuring out what the code is meant to do - it looks like it's mostly error handling code, and the meat of the function is lost in swathes of boilerplate; as I'm normally concentrating on what the code should normally do, not what it does when there's an exceptional condition, I find the sheer quantity of effort I have to put in to read your code wasteful.

Of course, this is not to say that exceptions and C++ are perfect - whoever implements auto_ptr and spinlock_t::guard has to give me a suitable level of exception safety (no-throw or strong guarantees, possibly basic guarantee, to use the jargon). However, I personally find that it's not that hard to meet the needed guarantees for exception safety, and the resulting increase in code clarity is well worth the effort.

Better Than Exceptions

Posted Jun 2, 2010 12:54 UTC (Wed) by gowen (guest, #23914) [Link]

If you think code with a series of
do { 
  if(foo) break; 
} 
constructs can be described as structed with "no gotos" then you have massively failed to understand what's bad about goto's. Give me the
if(error) goto cleanup_and_exit;
.
.
return SUCCESS;

cleanup_and_exit:
 free(ptr);
 return -E_NO_CLUE_ABOUT_STRUCTURED_PROGRAMMING;
idiom any day.

Better Than Exceptions

Posted Jun 2, 2010 15:51 UTC (Wed) by gek (guest, #18143) [Link] (1 responses)

This example reminds me of the "obvious solution" which leads you to the tar pit .

Better Than Exceptions

Posted Jun 2, 2010 19:15 UTC (Wed) by mjthayer (guest, #39183) [Link]

> This example reminds me of the "obvious solution" which leads you to the tar pit .

Somehow I feel that this must also be an answer to the original question of "what is wrong with exceptions" (in C++). That they are sufficiently non-obvious to use that almost twenty years after they were first introduced electrons are still being spend explaining how to use them safely, and people still feel the need to debate on lwn.net whether or not they are a good thing. I can't put it down entirely to people being stuck in the past.

Better Than Exceptions

Posted Jun 3, 2010 1:43 UTC (Thu) by jamesh (guest, #1159) [Link]

The version using goto is a lot more readable.

While it is possible to write spaghetti code with gotos, they're also the most concise and readable way of implementing the equivalent of a "try {} finally {}" block in C. So there is no real reason to avoid their use in error handling of this type.

GCC begins move to C++

Posted Jun 1, 2010 15:53 UTC (Tue) by dgm (subscriber, #49227) [Link] (17 responses)

The basic problem with exceptions is that they can pop up anywhere, changing control flow. Doing this in an existing code base, and doing it right, is really _hard_, not unlikely trying to make existing code thread safe.

Think for instance:

a = b + c();

this is a pretty innocent statement in C. Enter C++, where it can involve several constructors, destructors, method calls and overloaded operator invocations, all potentially throwing different kinds of exceptions, so you really should be doing:

try {
    a = b + c();
} catch (exception * e) {
    // STL exception
    manage_standard_exception(e);
} catch (LibException * e) {
    // unstandard exceptions thrown by a third party library
    manage_lib_exception(e);
} catch (...) {
    // catch everything else...
    manage_unknown_exception();
}

Exception safe code doesn't happen by chance. You have to plan for it, be careful and program defensively, always asking yourself "what if an exception is thrown just here?". It doesn't matter that It cannot happen today, because code evolves and can very well happen tomorrow.

If you try to add exceptions as an afterthought the probability that you will miss several critical places is high.

GCC begins move to C++

Posted Jun 1, 2010 16:11 UTC (Tue) by rev (guest, #15082) [Link] (11 responses)

Sorry, but you are wrong. The very same applies when you don't use exceptions. You have to constantly ask yourself questions like 'does this function return an error code?'. You have to deal with or delegate such error codes at virtualy every line of code you write.

It's just that exceptions automate and hide what you would otherwise write explicitly: return error codes to a point in the call tree where the error is handled. They are a language construct that expresses exactly what you would otherwise have to tediously code.

In more recent langauges exception than C++ have the benefit of having to handle an exception: failing to do so is a compile error. Whereas with classic error handling it is easy to forget to deal with an error situation.

GCC begins move to C++

Posted Jun 1, 2010 16:36 UTC (Tue) by dgm (subscriber, #49227) [Link] (10 responses)

return codes don't change control flow. That's the reason avik had to introduce auto_ptr<> and RAII besides exceptions in his example above.

consider for instance:

void f(void)
{
    T * p = malloc (sizeof (T));
    g(p);
    free(p);
}

This code does not leak memory in C, but can leak in C++ with exceptions.

Remember we are talking about a _large_ amount of C code that was not written with exceptions in mind.

GCC begins move to C++

Posted Jun 1, 2010 17:03 UTC (Tue) by avik (guest, #704) [Link] (2 responses)

It's possible to transition incrementally:

1. Pick a function
2. Modify its callers to expect exceptions from it (adding try/catch if necessary)
3. Convert it to throw exceptions instead of returning error codes
4. Modify it to expect exceptions from its callees (removing try/catch if necessary)
5. Repeat

If correctly applied, an entire code base can be converted with very little explicit try/catch blocks remaining.

GCC begins move to C++

Posted Jun 1, 2010 17:23 UTC (Tue) by dgm (subscriber, #49227) [Link] (1 responses)

It's certainly doable, but I would bet the cost is too high to make it worthy. I believe the GCC code base is above one million LOC. Assuming half include a function call, the task looks daunting.

GCC begins move to C++

Posted Jun 1, 2010 18:01 UTC (Tue) by avik (guest, #704) [Link]

I have successfully applied this technique to a code base that was about a quarter of that size (though considerably less gnarly). Whether it will be worth the effort or not is very subjective.

GCC begins move to C++

Posted Jun 2, 2010 11:13 UTC (Wed) by gowen (guest, #23914) [Link]

How about...

void f(void)
{
    T p;
    g(&p);
}

GCC begins move to C++

Posted Jun 2, 2010 18:01 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

This code will most probably SEGFAULT in C if allocation fails and malloc() returns NULL.

Is it better than a thrown exception?

You are right of course...

Posted Jun 3, 2010 21:07 UTC (Thu) by khim (subscriber, #9252) [Link]

This is exactly why you CAN'T write such code in gcc. malloc is forbidden there at include file level. Instead you'll use xmalloc - it can not ever return NULL, so there are no need to check the error code.

malloc() failure

Posted Jun 4, 2010 9:46 UTC (Fri) by jrn (subscriber, #64214) [Link] (1 responses)

On Linux, this is true but mostly irrelevant. Most systems overcommit (it is hard to find a saner thing to do if the user wants large programs to be able to fork() but does not provide the swap to back them) and malloc almost never fails.

malloc() failure

Posted Jun 7, 2010 9:47 UTC (Mon) by nye (guest, #51576) [Link]

There are other reasons malloc can fail beyond having no free memory. In zfs-fuse they recently had to add code to increase max_map_count dynamically because ZFS makes so many allocations that it could easily blow right past the default limit, causing memory allocation failures.

GCC begins move to C++

Posted Jun 3, 2010 5:44 UTC (Thu) by njs (guest, #40338) [Link] (1 responses)

That code is incorrect in C -- it fails to check g's return value for an error code. This may be (often is) a *much* more serious error than a leak.

You're assuming that g-written-in-C cannot fail, while g-written-in-C++ can, and then saying that that makes C better... which I guess is true given your assumptions, but if you have a method to write error-free code in C then you should share *that*, it'd be a much more compelling argument!

GCC begins move to C++

Posted Jun 3, 2010 16:41 UTC (Thu) by dgm (subscriber, #49227) [Link]

Why? Maybe I don't care if there is a problem with g, or I could do nothing sensible.

What I assume is that g-written-in-C cannot change the flow of control. That assumption doesn't hold in C++ with exceptions. That doesn't make C better, just different. Different enough, in fact, that converting C code straight to C++ is not that easy.

By the way:

@gowen: it's just an example.
@Cyberax: maybe g() can handle the NULL case gracefully, as it should if properly written.

GCC begins move to C++

Posted Jun 2, 2010 1:31 UTC (Wed) by dvdeug (subscriber, #10998) [Link] (4 responses)

If you convert the C statement "a = b + c();" directly into C++, the only place where exceptions can come up is if c() suddenly throws exceptions. If the causes don't matter then make c() not throw exceptions--if you don't control the source, you can always add an inline wrapper around it. If it does matter, then for a straightforward conversion you don't need to change anything and both the C and C++ programs will do poorly defined, unpleasant things in the presence of errors.

GCC begins move to C++

Posted Jun 2, 2010 13:53 UTC (Wed) by dgm (subscriber, #49227) [Link] (3 responses)

> If you convert the C statement "a = b + c();" directly into C++, the only place where exceptions can come up is if c() suddenly throws exceptions.

You would be surprised. I recommend you to read this http://www.gotw.ca/gotw/020.htm

In this concrete example:

1. c() can indeed throw an exception in the code, but can also propagate uncaught exceptions from any code called within, possibly including constructors.
2. + can be an overloaded operator, and thus can also throw.
3. = can need to create a temporary object, whose constructor can also throw.

AFAIK, the only thing that cannot throw is "b".

GCC begins move to C++

Posted Jun 2, 2010 14:37 UTC (Wed) by mjthayer (guest, #39183) [Link]

> In this concrete example:
> 1. c() can indeed throw an exception in the code, but can also propagate uncaught exceptions from any code called within, possibly including constructors.
> 2. + can be an overloaded operator, and thus can also throw.
> 3. = can need to create a temporary object, whose constructor can also throw.

I think the OP was talking about code directly converted from C, so those things should not apply.

GCC begins move to C++

Posted Jun 3, 2010 0:24 UTC (Thu) by dvdeug (subscriber, #10998) [Link] (1 responses)

If either = or + in a = b + c(); can throw exceptions, then it's not the same code. You're looking at something like

a = copy_foo (add_foo (b, c()));

in C. If C++ would have thrown an exception, then a will have an error code in it (and a will have to support error values, and that value will have to be checked just like you have to catch an exception), a will have trash in it (and you'll have to check it--if you can) or the whole thing will crash, like if copy_foo assumes that add_foo can't return errors or trash.

GCC begins move to C++

Posted Jun 3, 2010 16:31 UTC (Thu) by dgm (subscriber, #49227) [Link]

Exactly, remember that it could not be the case initially, but the project will hopefully continue evolving, and thus the assumptions that made the code correct will change.

That's basically the problem. Adding exceptions to an established code base changes basic assumptions programmers would probably have made (nobody in his right mind would lose time writing exception-safe code if your language has no exceptions!).

The coding standards are the least of your worries!

Posted Jun 1, 2010 11:50 UTC (Tue) by rev (guest, #15082) [Link] (17 responses)

For programmers going from C to C++ the most important issue is dealing with OO.

It is very easy to abuse C++'s OO features. The most common mistake is using inheritance where it not appropriate. Particularly when the relationship between two concepts is 1:1, programmers coming from C tend to use inheritance where composition is in order.

For instance, a HTTP connection has a TCP connection. For C programmers it is tempting to make a class representing a HTTP connection inherit from a class representing a TCP connection.

How can you see that this is wrong?

Well, suppose one day you need a HTTP connection over some other transport mechanism. Unfortunately your HTTP connection is intimately tied to a TCP connection (being a subclass it is a special kind of TCP connection after all). You cannot have a HTTP connection over some other transport mechanism without rewriting your HTTP connection class. Suppose you want to make your classes suitable for IPv6. You not only need to modify your TCP connection class but also your HTTP connection class.

Rather, you should supply the HTTP connection class (through the ctor) with an object representing a transport layer connection. (I.e. use composition). It has methods to read and write data, but whether it is a connection over TCP over IPv4, IPv6, or over some other transport layer, you don't care about. (Well, you also are likely to need to find a common interface for setting connection parameters dynamically, BTW. Finding commonalities amongst simmilar concepts is a common meme in OO.)

I did not make the above example up. It comes actually from a C++ library from the GNU project. The name escapes me. Sorry to say it was terribly designed. To get at the headers of a HTTP request, one had to subclass a HTTP connection class. There was no class representing a HTTP request to which one could simply say `getHeaders()'. It had knowledge about IPv6 in several layers of the class hierarchy.

Another common mistake is inherit to data rather than behavior. Having a class 'ellipse' inherit from a class 'circle' is a classic example. Rather `circle' and `ellipse' should both inherit from a class `shape' having a pure virtual method `draw()' that is implemented differently in its subclasses.

Needless to say the above abuse gets worse with every layer of subclassing one uses.

Some guiding principles:

  • Use inheritance only when you are dealing with similar but different behaviors. I.e. only when one inherits to implement pure virtual functions.
  • Related: favor composition over inheritance.
  • Don't use setters, unless one deals with a naturally dynamically alterable property of an object. The hostname of a TCP connection should not be set using a setter (once connected, it is immutable). It should be ctor supplied.
  • And related, all properties that are required to bring an object into a meaningful state should be supplied through the ctor.
  • Isolate responsibilities into a class.
  • Write down every piece of behavior only once.
  • Variables are private and never public. They may be protected when subclasses need access to them.
  • Avoid using `friend'. Don't use private and protected inheritance, these are meaningless.
  • Don't use multiple inheritance. It is a can of worms. When you think you need it, you likely have violated the first two principles.
Sorry for the sermon. It's just that it gets to my nerves that when programmers talk about writing maintainable code they jump to the relatively minor issues showing to have a blind spot for the issues that really matter.

The coding standards are the least of your worries!

Posted Jun 1, 2010 16:34 UTC (Tue) by Ed_L. (guest, #24287) [Link] (5 responses)

For the most part I agree, which is probably why Mark suggested limiting or forbidding inheritance in the first GCCC++ Guideline draft. (But I doubt it will fly). Composition vs. Inheritance is a basic question to all OO design, frequently aliased as "ISA vs. HASA". Prsumably, GCC already has some decent OO hacks to guide any OO "newbies" which in this particular context takes on a whole 'nother meaning as I 'spect the hardline GCC C hacks have eschewed OO because they are already aware of most of its pitfalls and simply don't want to deal with them. I expect some lively debate.
And related, all properties that are required to bring an object into a meaningful state should be supplied through the ctor.
Yabut here you run up against the "do you allow exceptions and where?" question,as well as your local definition of "meaningful state." Personally, I use exceptions. Except I try to avoid throwing from a constructor, because that requires a try block around the entire scope of the object. Rather, if an object needs to perform some exceptional operation in order to be initialized (e.g. allocating memory) I usually write a separate throwable Init() method that sets a private is_init flag to let all other methods know whether the object is ready to use. But (a) this isn't always possible, (b) I haven't investigated whether auto_ptr might be used to limit a constructor try block, and (c) YMMV anyway.

The Guide *will* be interesting.

The coding standards are the least of your worries!

Posted Jun 1, 2010 17:04 UTC (Tue) by Ed_L. (guest, #24287) [Link] (2 responses)

[Edit]
Yes, I meant the try block must surround the scope of any auto object whose constructor might throw. Certainly

C_Bar *pfoo;
try{
pfoo = new(C_Bar);
}catch(...){};

will work just fine. But I *like* auto objects....

And yes, if used an Init() method may return a status flag rather than throw an exception.

The coding standards are the least of your worries!

Posted Jun 1, 2010 17:13 UTC (Tue) by Ed_L. (guest, #24287) [Link]

Or even

pfoo = new C_Bar();

(Its what I get for writing code fragments without a compiler...)

The coding standards are the least of your worries!

Posted Jun 2, 2010 18:27 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link]

This is exceedingly bad design. Any good C++ programmer will write:

std::auto_ptr<C_BAR> bar(new C_Bar());

...or something like it. It's exception- and leak-proof.

In one of my projects we had a 'no naked new' rule. So the result of EACH 'new' must be wrapped in a smart pointer (there were only a few carefully audited places where this policy was relaxed). Worked wonders.

The coding standards are the least of your worries!

Posted Jun 1, 2010 17:49 UTC (Tue) by jwakely (subscriber, #60262) [Link]

> Personally, I use exceptions. Except I try to avoid throwing from a constructor,

This (not uncommon) approach amuses me. Handling errors in constructors was one of the motivations for adding exceptions to C++

> because that requires a try block around the entire scope of the object.

Only if you need to catch the exception in the same scope as the object. Usually I don't need to do that and let the exception exit the current scope (with destructors doing whatever cleanup is needed)

As has been suggested, adding exceptions to a large codebase that wasn't written with them in mind can cause problems. New code can be made exception-safe from the start and so there is less reason to not use exceptions. Writing exception-safe code is *not* difficult, it's just different.

The coding standards are the least of your worries!

Posted Jun 2, 2010 19:55 UTC (Wed) by rev (guest, #15082) [Link]

Well I may have jumped to conclusions about the OO awareness of the GCC team. If that is the case, sorry.

I've become a little touchy on the issue having seen so many C programmers struggle and failing with OO and seen so many programmers when asked about code quality jump right into detail issues like naming conventions and where to place the opening brace. Significant issues but the overall issues are way more fundamental.

See my post elsewhere for a comment on the guidelines.

The coding standards are the least of your worries!

Posted Jun 1, 2010 22:45 UTC (Tue) by Tobu (subscriber, #24111) [Link] (1 responses)

Good guidelines. I especially agree with the ones about keeping variables private not making things mutable lightly.

I'd like to amend the rule about multiple inheritance though; besides an optional base class, it is perfectly fine to inherit from a number of mixins. For example, any class that deals with a non-copyable system resource (such as a socket) should add boost::noncopyable (or the local equivalent) to its parents. And I recall friends can help making data more private to the world at large, and should be allowed to help write things like output operators that are closely related to the class.

The coding standards are the least of your worries!

Posted Jun 2, 2010 20:18 UTC (Wed) by rev (guest, #15082) [Link]

@Ed_L & Tobu

Surely. These are guidelines. And my list is not complete I might add. I like to formulate them a bit strict. Firstly to make them clear. Secondly to steer programmers with little OO experience away from the abuse their instinct seems to drives them to. You are allowed to break the rules when you understand them a kind of being the motto.

There will always be situations where we have to break the rules. Rules cannot deal 100% with all the details a quirks of reality.

And, of course, there are always situations where the nature of the underlying problem is such that deviation from the rules is the required solution, such as building a class hierarchy, or using friends.

The coding standards are the least of your worries!

Posted Jun 2, 2010 18:50 UTC (Wed) by ikm (guest, #493) [Link] (7 responses)

> Don't use private and protected inheritance, these are meaningless.

And why is that, exactly?

The coding standards are the least of your worries!

Posted Jun 2, 2010 19:48 UTC (Wed) by rev (guest, #15082) [Link] (6 responses)

Because they amount to delegation to a private/protected member. What you are doing when using private/protected inheritance is writing a HAS-A relationship as if it where an IS-A relationship.

The coding standards are the least of your worries!

Posted Jun 2, 2010 19:54 UTC (Wed) by ikm (guest, #493) [Link] (5 responses)

> writing a HAS-A relationship as if it where an IS-A relationship.

Yes, that's exactly what I'm doing. What's wrong with it, exactly?

The coding standards are the least of your worries!

Posted Jun 2, 2010 20:09 UTC (Wed) by ikm (guest, #493) [Link] (2 responses)

And by the way, it is still an IS-A relationship, since I can access inherited protected members and override virtual functions. This relation isn't exposed to the outside world, though. Imagine a Class which is a Werewolf, but it doesn't actually want anybody to know about that fact :)

What I want to say is, protected/private inheritance makes sense. A classic example is when you want to make an active object:

class Foo: QThread
{
...
private:
virtual void run();
}

You don't want start() and stop() to be exposed to the outside world, and yet you want it to be a thread (by being able to override the actual thread function).

Lastly, stating that something in the language is plain "meaningless" is implying that the people who agreed to add it to the standard are morons. This rarely makes sense.

The coding standards are the least of your worries!

Posted Jun 2, 2010 21:21 UTC (Wed) by rev (guest, #15082) [Link] (1 responses)

Well if you want your class to be a Werewolf but don't want the world to know about it your class really isn't a werewolf but has-a werewolf.

Your Foo class in your example is now a QThread except that it isn't: the outside world cannot call any of the methods that make it a QThread.

That you find yourself forced to use protected inheritance in your example is due to a modelling flaw in the QThread class. Not unlike the situation in which I found myself forced to subclass a HTTPConnection only to be able to get to the headers of a HTTP request. The QThread class should have something like a Worker member having the run() method. You implement a Worker (ie subclass it and implement the virtual run() function). You have a Qthread instance var, feed it your worker implementation, and call start() on your QThread.

A useful way, BTW, to find out whether you have woven different concepts into a single class, that thus should be decomposed into different classes is this:

Picture all the instance variables of your class and it superclass on one side. Likewise, picture all functions on the other side. Now draw a line between a function and a variable when said function uses that variable, If the resulting class now tends to consists of several unconnected graphs you have reason to consider decomposing your classes: the belong to the same hierarchy but appear to be unrelated.

Applied to the QThread example: the designers of the QThread should have asked themselves the question: is it desirable that an implementation of the run() function have access to the protected instance variables? The answer is no. Because QThread deals with the machinery of threading while the run() functions deals with matters of the user's problem domain, like reading messages from a queue. The stuff in the run() function is therefore another beast that needs to sit in its own class.

I find your 'moron' remark rather odd. Firstly, no language is perfect, no language designer is. Secondly, C++ was conceived at a time when understanding of OO was, by today's standards, rather limited.

The coding standards are the least of your worries!

Posted Jun 2, 2010 21:34 UTC (Wed) by ikm (guest, #493) [Link]

> Well if you want your class to be a Werewolf but don't want the world to know about it your class really isn't a werewolf but has-a werewolf.

Maybe to the world, but not to him. To him, he is a werewolf. At night, when there is a full moon, he transforms into an unholy beast and hunts basic_strings. The world doesn't have to know.

The coding standards are the least of your worries!

Posted Jun 2, 2010 20:38 UTC (Wed) by rev (guest, #15082) [Link]

Well.. a HAS-A relationship being modelled as an ISA-A?

Please see my HTTP connection example in my post http://lwn.net/Articles/390256/ to see why this is, in general bad.

The coding standards are the least of your worries!

Posted Jun 2, 2010 20:57 UTC (Wed) by rev (guest, #15082) [Link]

I forgot.

To recapitulate:

When you write an HAS-A as an IS-A you really tied two concepts into one which are not.

Suppose you have class D: C

Suppose later you need another flavor of C, say B, or you have to rewrite D You will have to rewrite D and its relationship.

Now write:

class D {
private:
C c;
}

You one day need a B. Introduce an abstract base class A for both B and C:

class D {
private:
A a;
}

Now you have the advantage that you can easily change 'a' to be a B or a C.

BTW, there's people that take this advantage to the next level. They recognized that A has become a dependency of D. And rather than hard-wiring this dependency in your code created tools that allow you to specify in a configuration file whether 'a' should be an B or a C. Some day another kind of A is needed? Write a class E, deploy it, modify the config file and there you go.

The coding standards are the least of your worries!

Posted Jun 2, 2010 23:41 UTC (Wed) by daglwn (guest, #65432) [Link]

Some guiding principles:

Most of these are good. A few comments and additional suggestions follow.

  • Use inheritance only when you are dealing with similar but different behaviors. I.e. only when one inherits to implement pure virtual functions.

This seems too strict to me. There are reasons to inherit beyond overriding virtual functions. The Curiously Recurring Template Pattern is a prime example (replacing dynamic polymorphism with static polymorphism).

  • Isolate responsibilities into a class.

I think I know what you mean, but we have to be careful with how we define "class" or "object." A class does not necessarily have all of its functionality in member functions (see below).

  • Write down every piece of behavior only once.

If I understand your meaning, this is good in every language. Factoring out common behavior is always important. If I've missed your point, please follow up. It's good to learn from others.

  • Variables are private and never public. They may be protected when subclasses need access to them.

I would go even stronger than that. Rather than make the variables protected, make the accessors (possibly getters/setters) protected. That way you minimize impacts of data member changes.

  • Avoid using `friend'. Don't use private and protected inheritance, these are meaningless.

This also seems overly restrictive. In general, yes, one doesn't need private or protected inheritance, but they have their uses, especially when composing behaviors from several classes (aha! see the next point :) ).

  • Don't use multiple inheritance. It is a can of worms. When you think you need it, you likely have violated the first two principles.

This is by far my biggest pet peeve of most "good C++ practice" lists. MI is incredibly useful. What one usually doesn't want to do is create a diamond hierarchy where base classes have data members. The problem with data members in diamond hierarchies is the ambiguity of initialization. The most-derived class needs to take care of initializing base class data members, which breaks encapsulation. But MI is very, very useful for imparting properties and/or varying behaviors on a common class (template). Mixins are wonderful things. MI gets used all over the place when template metaprogramming is involved (c.f. Modern C++ Design, boost, etc.).

I would add these guidelines:

  • Make all virtual functions private with a public API when necessary. This allows one to insert various behaviors (debug messages, etc.) in the public API once and have it apply to all invocations of the (private) virtual function.

Example:

class Base {
private:
  virtual void doItImpl(void) = 0;

public:
  void doIt(void) {
    debug("Calling doIt!");
    doItImpl();
  }
};

class Foo : Base {
private:
  void doItImpl(void) {
    doSomething();
  }
};

  • Enforce const correctness (applies to C as well).
  • Use RAII liberally.
  • Use exceptions.
  • Use type generators to specify data structure types. Do not hard-code std::map<>, etc. When C++0x is ready, use template aliases. This allows one to easily swap out data structure implementations and specialize them for various types. This is quite useful when working to optimize runtime.

Example:

template<typename Key, typename Value>
struct MapGenerator {
  typedef std::map<Key, Value> type;
};

template<typename Value>
struct MapGenerator<int, Value> {
  typedef MyFancyEfficientIntMapper<Value> type;
};

typedef MapGenerator<double, std::string>::type DoubleStringMap;
typedef MapGenerator<int, std::string>::type IntStringMap;

  • Prefer free functions. This may seem counterintuitive, but actually makes generic programming easier. Compare boost.Range(Ex) and the standard algorithms. Having freestanding begin() and end() functions is super useful. The same pattern applies to many intrinsic operations. Making them free functions instead of members means that one can adapt existing classes to the interface without having to muck around with changing the class (adding member functions, etc.).

And my number one guideline:

  • Do not write code unless you have to. This may seem obvious but there are countless examples of projects that refuse to use existing libraries like boost and end up reimplementing them. Badly. Reimplement the stuff if necessary, by all means (to solve proven performance bottlenecks, for example), but make a darn good case for doing so. This is right up there with "Don't optimize prematurely."

C+

Posted Jun 1, 2010 19:35 UTC (Tue) by coriordan (guest, #7544) [Link] (1 responses)

So, someone will spec it up on the wiki, then a bright spark will suggestion putting checks in the compiler, and there, we have a new "minimal C++" language in gcc.

And if minimal C++ is better for gcc, other projects will consider it...

C+

Posted Jun 1, 2010 22:53 UTC (Tue) by Tobu (subscriber, #24111) [Link]

One of the existing warning flags, -Weffc++, is actually a set of C++ guidelines. It's more blacklist than whitelist. It's a good addition to the “make the compiler check your code” toolkit.

GCC begins move to C++

Posted Jun 3, 2010 10:44 UTC (Thu) by sylware (guest, #35259) [Link] (1 responses)

A proper C++ compiler is a monster compared to a proper C compiler, and that without any optimization framework. I dislike my software being dependent on a piece of software that is that hard to rewrite. "A kernel is already enough" sort of illustrate my opinion. I know many other components have faulted (from my point of view) the same way, but I feel better to remove them than add more. Till gcc does not need a C++ compiler to compile its C compiler, I'll stay quiet. If the steering comity decided othewise and it does happen, I will start to depart from gcc or try to make gcc C++ parts needed for the C optimizing compiler optional. In the case I depart from it, I'll feel the pain of losing the optimization part of gcc, but I'll try my best to depart from it. There are not many alternatives, since I do contribute GNU GPL only code...

Additionnaly, as I percieve it (I was a C++ coder), C++ helps too much (way more than C) coders to create brain damaged object orientish designs that ends up kludge/bloat. The mental dicipline in order to avoid such deviance can perfectly be applied to C too, then better be C which is easier to write a compiler for.

This is a part of my personal perception of that specific issue which explains for a bigger part my choices and my behavior: for all software use cases, I'm looking to reduce as much as possible size/complexity of the software stack. I really dislike the feature freakness and "over abstraction" many coders are falling for (I try to avoid it myself, it's hard). Down to the C++ issue, actually I do feel the same way for all langages. C is the less worse choice in my opinion.

GCC begins move to C++

Posted Jun 3, 2010 13:50 UTC (Thu) by mpr22 (subscriber, #60784) [Link]

C inflicts syntactic salt on me every time I want to do arithmetic on ordered pairs of integers; C++ doesn't :)

GCC begins move to C++

Posted Jun 3, 2010 17:20 UTC (Thu) by cdmiller (guest, #2813) [Link] (1 responses)

Huh, I was taught many years ago OOP is a style, independent of the programming language. See: http://openlibrary.org/books/OL1877952M/Object-oriented_a... for an example of this in action. Further, there are some applications where OOP will work well, and others OOP will overly complicate.

GCC begins move to C++

Posted Jun 3, 2010 21:09 UTC (Thu) by daglwn (guest, #65432) [Link]

It's true that "OO" (it's a very overloaded term) can be done in many languages. The important thing is how much the compiler/interpreter can help you. I can write OO-style code in C with virtual functions and everything but then I have to implement my own vtables (violating the "don't write code you don't have to" rule) and I don't get the safety of access specifications, among other things.

The traditional UNIX paradigm of "everything is a file" is a fine example of OO design. What language it's implemented in is of little importance.

To me the important considerations for language choice do not include how powerful it is (they're all Turing complete in the end) but rather how well it maps to the problem domain and how many tools exist to help me find and fix problems.

GCC begins move to C++

Posted Jun 4, 2010 15:05 UTC (Fri) by brinkmd (guest, #45122) [Link] (2 responses)

No mention of C++ should go without mention of the C++ FQA (Frequently Questioned Answers):

http://yosefk.com/c++fqa/

It's a rebuttal of the C++ FAQ, and is a much more honest and useful explanation of the pitfalls of C++ than any other resource I found. If you ever thought while programming C++: "I can't be the first to try to do that, why is this so hard?", then the C++ FQA comes to the rescue.

GCC begins move to C++

Posted Jun 4, 2010 15:40 UTC (Fri) by daglwn (guest, #65432) [Link] (1 responses)

No, it's really not very useful. Any FAQ (or "FQA") that starts with an ideological statement about its purpose being to "convince people...There is no reason to use C++ for new projects" is of course inherently biased. It's nothing more than childish whining (or a childish pissing contest, take your pick).

Yes, there are some issues with C++. Many are even being addressed in C++0x. But this "FQA" demonstrates a fundamental misunderstanding of the language, its design and its purpose. It's worse than useless. It will actually steer people in the wrong direction. Not just away from C++ (there are many cases where C++ is not appropriate) but away from sound technical analysis (maybe I don't need garbage collection) and into blind faith in highly dynamic systems.

GCC begins move to C++

Posted Jun 7, 2010 10:01 UTC (Mon) by etienne (guest, #25256) [Link]

Maybe the FAQ of the FQA worth a read?
http://yosefk.com/c++fqa/faq.html
For me, I thank brinkmd for this FQA link, worth a read.


Copyright © 2010, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds