From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Thread: 103376,1888e8caa20a2f2d,start X-Google-Attributes: gid103376,public X-Google-Language: ENGLISH,ASCII-7-bit Path: g2news1.google.com!news4.google.com!border1.nntp.dca.giganews.com!nntp.giganews.com!newsfeed00.sul.t-online.de!t-online.de!130.59.10.21.MISMATCH!kanaga.switch.ch!news-zh.switch.ch!switch.ch!cernne03.cern.ch!cern.ch!news From: Maciej Sobczak Newsgroups: comp.lang.ada Subject: Controlled types and exception safety Date: Wed, 30 Nov 2005 14:57:07 +0100 Organization: CERN - European Laboratory for Particle Physics Message-ID: NNTP-Posting-Host: abpc10883.cern.ch Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit X-Trace: sunnews.cern.ch 1133359027 17562 (None) 137.138.37.241 X-Complaints-To: news@sunnews.cern.ch User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.12) Gecko/20050922 Red Hat/1.7.12-1.1.3.2.SL3 X-Accept-Language: en-us, en Xref: g2news1.google.com comp.lang.ada:6678 Date: 2005-11-30T14:57:07+01:00 List-Id: Hi, I try to understad how the Controlled types work and I spotted one "small issue" that makes it difficult to write exception-safe code. The "exception-safe" means that code behaves "correctly" in the presence of exceptions, for some chosen definition of "correctly". In C++ we define the following levels of exception-safety: - level 0 (no guarantee) - in the presence of exception, anything can happen, memory may become corrupted, data structures may become completely mangled, etc. - level 1 (basic guarantee) - in the presence of exception, no resources are leaked and objects are in a coherent, but not necessarily predictable state. - level 2 (strong guarantee) - in the presence of exception, the program state (to the relevant extent) remains unchanged. This is similar to the commit-or-rollback semantics known from databases. - level 3 (nothrow guarantee) - the code simply guarantees that there are no exceptions. Why is this classification useful? Let's say that I have an abstract data type that implements some data structure - a stack, for example. I can classify the stack's operations by assigning them any of the above four levels, so that I know what can be expected when an exception is thrown for any reason (like inability to allocate more memory, or alike). For example, if the Push method of the stack gives me the strong guarantee (level 2 above), then I *know* that by calling this method either the new element will be appended to the stack, or the stack will remain unchanged, so that even if the exception is thrown, I don't have to worry about the stack's internal consistency. This is useful. This is useful also in assignment operations. Since stack can be a dynamic data structure, assigning one stack object to another may involve destroying one existing data structure *and* creating a new one (a copy) in its place. Similarly, the quality implementation should provide the strong guarantee, so that I *know* that either the stack was properly copied, or there was a problem during assignment and an exception was thrown, but nothing changed in any of the objects involved. Let's say that I want to write a stack in Ada. Making it a Controlled type seems to be a good idea, so that we have hooks for initialization, adjusting and finalization. Let's say that I have two stack objects, X and Y: X, Y : Stack; These objects were populated with some data, so that each of them manages its own internal dynamic data structure. Now, I do this: X := Y; and the following happens (this is what I understand, please correct me if I'm wrong): 1. X is finalized. This allows me to clean up (free) its internal data. 2. Y is *shallow-copied* to X, so that in effect X and Y share their state. 3. X is adjusted. This allows me to duplicate its internal structure so that it becomes independent from Y. later: 4. Both X and Y are finalized. This allows me to clean up (free) their resources. For everything to work correctly it's important that two separate stack objects *never* share their internal dynamic data structure, otherwise bad things can happen. It would be also fine not to leak memory. Now, the interesting part: let's say that during adjustment (3.) some error happened (like low memory condition or whatever) that resulted in raising an exception (note: this exception might be actually raisen not by the stack code, but by the assignment operation of the stack elements, even somewhere in the middle of this process). Bad things will happen in subsequent finalization of those objects, unless I handle it by cleaning up everything that I already managed to duplicate (but still, this leaves me with the empty stack). I think that the inherent problem comes from the fact that the finalization of X was forced *before* its adjustment. The canonical C++ way is to *first* make a copy of new value (because this is when errors might occur, so that even if they occur, there was no change in the destination object) and *then* inject the duplicate into the destination object, getting rid of its old state (and this is assumed to be nothrow). The "Ada way" looks like selling the house *before* looking for the new one. What do you do to avoid surprises? -- Maciej Sobczak : http://www.msobczak.com/ Programming : http://www.msobczak.com/prog/