Date: Sun, 15 Feb 2009 20:19:23 +0100 From: Christoph Mallon <christoph.mallon@gmx.de> To: Marcel Moolenaar <xcllnt@mac.com> Cc: Andrew Reilly <andrew-freebsd@areilly.bpc-users.org>, Bruce Simpson <bms@incunabulum.net>, freebsd-current@freebsd.org, Andriy Gapon <avg@icyb.net.ua> Subject: Re: weeding out c++ keywords from sys/sys Message-ID: <49986ABB.5040207@gmx.de> In-Reply-To: <8EF8771C-76D8-4556-96B2-B97B35573CBD@mac.com> References: <4995BB1B.7060201@icyb.net.ua> <20090213231513.GA20223@duncan.reilly.home> <4997F105.5020409@icyb.net.ua> <499811DF.6030905@incunabulum.net> <20090215151318.0d17bfb9@ernst.jennejohn.org> <499835BE.3000705@gmx.de> <8EF8771C-76D8-4556-96B2-B97B35573CBD@mac.com>
next in thread | previous in thread | raw e-mail | index | archive | help
Marcel Moolenaar schrieb: > > On Feb 15, 2009, at 7:33 AM, Christoph Mallon wrote: >> More robust error handling and less tedious resouce management >> directly come to mind: >> Just look at normal C functions which allocate resources and have >> multiple points which can fail. They are the usual mess of if()s, goto >> error and lots of cleanup code. Further all this code looks pretty >> much the same in several modules. In C++ you write the resource >> handling code once (constructors/destructors) and then you cannot >> forget to clean up, because thanks to scoping and defined life ranges >> it happens automatically. > > While on the surface this looks better, under the hood > it's just the same. Worse in most likelihood, because Of course it's the same, all these languages are Turing-complete! > with C the programmer writes the logic that is known to > be needed (assuming no bugs). With C++ it's the compiler Assuming no bugs, the Ariane wouldn't have exploded, Sojourner would've worked perfectly from day one, Hubble wouldn't have needed a correcting lense, ... The whole idea is to lower the probability of making errors. The resource allocation scenario I sketched earlier is one example for that: Lots of tedious and very similar code, which makes the process of writing it error prone. > that generates code that handles all possible scenarios, > and goes beyond what is strictly needed -- as such the > cost tends to be higher, even when there are no errors > or exceptions. There is no fairy which magically makes code appear everywhere. All code which gets generated is clearly defined by the programmer: You write code for constructors and destructors. All inserted code is placed at the boundaries of life ranges of objects. When an object is created its constructor is called, when it is destroyed its destructor is called. The compiler keeps track where exactly the life ranges of objects begin and end and properly inserts the code there. The programmer determines the life ranges of the objects either by new/delete, aggregating objects in classes or creating local objects in function {} scope. in C you write something like that: typedef struct FOO { A* a; B* b; C* c; } FOO; FOO* Allocate_FOO() { FOO* f = malloc(*f); if (f == NULL) return NULL; lock(some_lock); f->a = allocate_A(); if (f->a == NULL) goto fail; f->b = allocate_B(); if (f->b == NULL) goto fail; f->c = allocate_C(); if (f->c == NULL) goto fail; unlock(some_lock); return f; fail: if (f->b) deallocate_B(f->b); if (f->a) deallocate_A(f->a); free(f); unlock(some_lock); return NULL; } or worse and more error prone but seen often: FOO* Allocate_FOO() { FOO* f = malloc(*f); if (f == NULL) return NULL; lock(some_lock); f->a = allocate_A(); if (f->a == NULL) { free(f); unlock(some_lock); return NULL; } f->b = allocate_B(); if (f->b == NULL) { deallocate_A(f->a); free(f); /* Nobody will notice this lock leak until allocating B * fails some day. */ return NULL; } f->c = allocate_C(); if (f->c == NULL) { /* Whoops, allocating resource B was added later and we * forgot to deallocate it here. */ deallocate_A(f->a); free(f); unlock(some_lock); return NULL; } unlock(some_lock); return f; } In C++ you write something like this: class FOO { public: FOO(); private: auto_ptr<A> a; auto_ptr<B> b; auto_ptr<C> c; }; FOO::FOO() { LockHolder l(some_lock); a = new A(); b = new B(); c = new C(); } There is no way to have a resource leak here: No matter how the constructor is left (regular return or exception), the lock is released via the destructor of the LockHolder. Also if any of the constructors of A, B or C throws an exception all objects created till this point are properly destroyed by the destructors of the auto_ptr<>s (per language specification in the reverse order of declaration in the class). I consider this a improvement: The code is more concise and writing it is less error prone. Oh, let's not forget deallocating FOO: void deallocate_FOO(FOO* f) { deallocate_C(f->c); deallocate_B(f->b); deallocate_A(f->a); } and the C++ version: /* No code needed, the default destructor of FOO already does The Right * Thing(TM), i.e. destroys all subobjects in reverse order of * declaration */ > I'm not saying this is a problem. All I'm saying is that > you move responsibility from the programmer to the compiler > and in general this comes at a (runtime_ cost. One we may > very well accept, mind you... Writing the code correctly once is still in the hands of the programmer. Copying the code where it is needed now is the job of the compiler. The compiler can do the more tedious parts and in this way help the programmer. Whew, this got longer than intended. Hopefully I could point out one of the benefits of C++. Please bear in mind this is no anti-C campaign. I just want to point out that C++ provides mechanisms to help writing better code.
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?49986ABB.5040207>