root/trunk/docsrc/cppdbc.dd

Revision 659, 9.7 kB (checked in by andrei, 4 years ago)

Changed ddoc extensions to dd and changed linux.mak accordingly

  • Property svn:eol-style set to native
Line 
1 Ddoc
2
3 $(COMMUNITY D's Contract Programming vs C++'s,
4
5     Many people have written me saying that D's Contract Programming
6     (DbC) does not add anything that C++ does not already support.
7     They go on to illustrate their point with a technique for doing DbC in
8     C++.
9     <p>
10
11     It makes sense to review what DbC is, how it is done in D,
12     and stack that up with what each of the various C++ DbC techniques
13     can do.
14     <p>
15
16     Digital Mars C++ adds
17     <a href="../ctg/contract.html">extensions to C++</a>
18     to support DbC, but they are not covered here because they are not
19     part of standard C++ and are not supported by any other C++ compiler.
20
21 <h2>Contract Programming in D</h2>
22
23     This is more fully documented in the D
24     <a href="dbc.html">Contract Programming</a> document.
25     To sum up, DbC in D has the following characteristics:
26
27     $(OL
28
29     $(LI The $(I assert) is the basic contract.
30     )
31
32     $(LI When an assert contract fails, it throws an exception.
33     Such exceptions can be caught and handled, or allowed to
34     terminate the program.
35     )
36
37     $(LI Classes can have $(I class invariants) which are
38     checked upon entry and exit of each public class member function,
39     the exit of each constructor, and the entry of the destructor.
40     )
41
42     $(LI Assert contracts on object references check the class
43     invariant for that object.
44     )
45
46     $(LI Class invariants are inherited, that means that a derived
47     class invariant will implicitly call the base class invariant.
48     )
49
50     $(LI Functions can have $(I preconditions) and $(I postconditions).
51     )
52
53     $(LI For member functions in a class inheritance hierarchy, the
54     precondition of a derived class function are OR'd together
55     with the preconditions of all the functions it overrides.
56     The postconditions are AND'd together.
57     )
58
59     $(LI By throwing a compiler switch, DbC code can be enabled
60     or can be withdrawn from the compiled code.
61     )
62
63     $(LI Code works semantically the same with or without DbC
64     checking enabled.
65     )
66
67     )
68
69 <h2>Contract Programming in C++</h2>
70
71 <h3>The $(TT assert) Macro</h3>
72
73     C++ does have the basic $(TT assert) macro, which tests its argument
74     and if it fails, aborts the program. $(TT assert) can be turned
75     on and off with the $(TT NDEBUG) macro.
76     <p>
77
78     $(TT assert) does not know anything about class invariants,
79     and does not throw an exception when it fails. It just aborts
80     the program after writing a message. $(TT assert) relies on
81     a macro text preprocessor to work.
82     <p>
83
84     $(TT assert) is where explicit support for DbC in Standard C++
85     begins and ends.
86
87 <h3>Class Invariants</h3>
88
89     Consider a class invariant in D:
90
91 ----------
92 class A
93 {
94     $(B invariant)() { ...contracts... }
95
96     this() { ... }  // constructor
97     ~this() { ... } // destructor
98
99     void foo() { ... }  // public member function
100 }
101
102 class B : A
103 {
104     $(B invariant)() { ...contracts... }
105     ...
106 }
107 ----------
108
109     To accomplish the equivalent in C++ (thanks to Bob Bell for providing
110     this):
111
112 $(CCODE
113 template<typename T>
114 inline void check_invariant(T& iX)
115 {
116 #ifdef DBC
117     iX.invariant();
118 #endif
119 }
120
121 // A.h:
122
123 class A {
124     public:
125 #ifdef DBG
126        virtual void invariant() { ...contracts... }
127 #endif
128        void foo();
129 };
130
131 // A.cpp:
132
133 void A::foo()
134 {
135     check_invariant(*this);
136     ...
137     check_invariant(*this);
138 }
139
140 // B.h:
141
142 #include "A.h"
143
144 class B : public A {
145     public:
146 #ifdef DBG
147     virtual void invariant()
148     {   ...contracts...
149        A::invariant();
150     }
151 #endif
152        void bar();
153 };
154
155 // B.cpp:
156
157 void B::barG()
158 {
159     check_invariant(*this);
160     ...
161     check_invariant(*this);
162 }
163 )
164
165     There's an additional complication with $(TT A::foo()). Upon every
166     normal exit from the function, the $(TT invariant()) should be
167     called.
168     This means that code that looks like:
169
170 $(CCODE
171 int A::foo()
172 {
173     ...
174     if (...)
175     return bar();
176     return 3;
177 }
178 )
179
180     would need to be written as:
181
182 $(CCODE
183 int A::foo()
184 {
185     int result;
186     check_invariant(*this);
187     ...
188     if (...)
189     {
190     result = bar();
191     check_invariant(*this);
192     return result;
193     }
194     check_invariant(*this);
195     return 3;
196 }
197 )
198
199     Or recode the function so it has a single exit point.
200     One possibility to mitigate this is to use RAII techniques:
201
202 $(CCODE
203 int A::foo()
204 {
205 #if DBC
206     struct Sentry {
207        Sentry(A& iA) : mA(iA) { check_invariants(iA); }
208        ~Sentry() { check_invariants(mA); }
209        A& mA;
210     } sentry(*this);
211 #endif
212     ...
213     if (...)
214     return bar();
215     return 3;
216 }
217 )
218
219     The #if DBC is still there because some compilers may not
220     optimize the whole thing away if check_invariants compiles to nothing.
221
222 <h2>Preconditions and Postconditions</h2>
223
224     Consider the following in D:
225
226 ----------
227 void foo()
228     in { ...preconditions... }
229     out { ...postconditions... }
230     body
231     {
232     ...implementation...
233     }
234 ----------
235
236     This is nicely handled in C++ with the nested Sentry struct:
237
238 $(CCODE
239 void foo()
240 {
241     struct Sentry
242     {   Sentry() { ...preconditions... }
243     ~Sentry() { ...postconditions... }
244     } sentry;
245     ...implementation...
246 }
247 )
248
249     If the preconditions and postconditions consist of nothing
250     more than $(TT assert) macros, the whole doesn't need to
251     be wrapped in a $(TT #ifdef) pair, since a good C++ compiler will
252     optimize the whole thing away if the $(TT assert)s are turned off.
253     <p>
254
255     But suppose $(TT foo()) sorts an array, and the postcondition needs
256     to walk the array and verify that it really is sorted. Now
257     the shebang needs to be wrapped in $(TT #ifdef):
258
259 $(CCODE
260 void foo()
261 {
262 #ifdef DBC
263     struct Sentry
264     {   Sentry() { ...preconditions... }
265     ~Sentry() { ...postconditions... }
266     } sentry;
267 #endif
268     ...implementation...
269 }
270 )
271
272     (One can make use of the C++ rule that templates are only
273     instantiated when used can be used to avoid the $(TT #ifdef), by
274     putting the conditions into a template function referenced
275     by the $(TT assert).)
276     <p>
277
278     Let's add a return value to $(TT foo()) that needs to be checked in
279     the postconditions. In D:
280
281 ----------
282 int foo()
283     in { ...preconditions... }
284     out (result) { ...postconditions... }
285     body
286     {
287     ...implementation...
288     if (...)
289         return bar();
290     return 3;
291     }
292 ----------
293
294     In C++:
295
296 $(CCODE
297 int foo()
298 {
299 #ifdef DBC
300     struct Sentry
301     {   int result;
302     Sentry() { ...preconditions... }
303     ~Sentry() { ...postconditions... }
304     } sentry;
305 #endif
306     ...implementation...
307     if (...)
308     {   int i = bar();
309 #ifdef DBC
310     sentry.result = i;
311 #endif
312     return i;
313     }
314 #ifdef DBC
315     sentry.result = 3;
316 #endif
317     return 3;
318 }
319 )
320
321     Now add a couple parameters to $(TT foo()). In D:
322
323 ----------
324 int foo(int a, int b)
325     in { ...preconditions... }
326     out (result) { ...postconditions... }
327     body
328     {
329     ...implementation...
330     if (...)
331         return bar();
332     return 3;
333     }
334 ----------
335
336     In C++:
337
338 $(CCODE
339 int foo(int a, int b)
340 {
341 #ifdef DBC
342     struct Sentry
343     {   int a, b;
344     int result;
345     Sentry(int a, int b)
346     {   this->a = a;
347         this->b = b;
348         ...preconditions...
349     }
350     ~Sentry() { ...postconditions... }
351     } sentry(a,b);
352 #endif
353     ...implementation...
354     if (...)
355     {   int i = bar();
356 #ifdef DBC
357         sentry.result = i;
358 #endif
359         return i;
360     }
361 #ifdef DBC
362     sentry.result = 3;
363 #endif
364     return 3;
365 }
366 )
367
368 <h2>Preconditions and Postconditions for Member Functions</h2>
369
370     Consider the use of preconditions and postconditions for a
371     polymorphic function in D:
372
373 ----------
374 class A
375 {
376     void foo()
377     in { ...Apreconditions... }
378     out { ...Apostconditions... }
379     body
380     {
381         ...implementation...
382     }
383 }
384
385 class B : A
386 {
387     void foo()
388     in { ...Bpreconditions... }
389     out { ...Bpostconditions... }
390     body
391     {
392         ...implementation...
393     }
394 }
395 ----------
396
397     The semantics for a call to $(TT B.foo()) are:
398
399     $(UL
400     $(LI Either Apreconditions or Bpreconditions must be satisfied.)
401     $(LI Both Apostconditions and Bpostconditions must be satisfied.)
402     )
403
404     Let's get this to work in C++:
405
406 $(CCODE
407 class A
408 {
409 protected:
410     #if DBC
411     int foo_preconditions() { ...Apreconditions... }
412     void foo_postconditions() { ...Apostconditions... }
413     #else
414     int foo_preconditions() { return 1; }
415     void foo_postconditions() { }
416     #endif
417
418     void foo_internal()
419     {
420     ...implementation...
421     }
422
423 public:
424     virtual void foo()
425     {
426     foo_preconditions();
427     foo_internal();
428     foo_postconditions();
429     }
430 };
431
432 class B : A
433 {
434 protected:
435     #if DBC
436     int foo_preconditions() { ...Bpreconditions... }
437     void foo_postconditions() { ...Bpostconditions... }
438     #else
439     int foo_preconditions() { return 1; }
440     void foo_postconditions() { }
441     #endif
442
443     void foo_internal()
444     {
445     ...implementation...
446     }
447
448 public:
449     virtual void foo()
450     {
451     assert(foo_preconditions() || A::foo_preconditions());
452     foo_internal();
453     A::foo_postconditions();
454     foo_postconditions();
455     }
456 };
457 )
458
459     Something interesting has happened here. The preconditions can
460     no longer be done using $(TT assert), since the results need
461     to be OR'd together. I'll leave as a reader exercise adding
462     in a class invariant, function return values for $(TT foo()),
463     and parameters
464     for $(TT foo()).
465
466 <h2>Conclusion</h2>
467
468     These C++ techniques can work up to a point. But, aside from
469     $(TT assert), they are not standardized and so will vary from
470     project to project. Furthermore, they require much tedious
471     adhesion to a particular convention, and add significant clutter
472     to the code. Perhaps that's why it's rarely seen in practice.
473     <p>
474
475     By adding support for DbC into the language, D offers an easy
476     way to use DbC and get it right. Being in the language standardizes
477     the way it will be used from project to project.
478
479 <h2>References</h2>
480
481     Chapter C.11 introduces the theory and rationale of
482     Contract Programming in
483     <a href="http://www.amazon.com/exec/obidos/ASIN/0136291554/classicempire">
484     Object-Oriented Software Construction
485     </a><br>
486     Bertrand Meyer, Prentice Hall
487     <p>
488
489     Chapters 24.3.7.1 to 24.3.7.3 discuss Contract Programming in C++ in
490     <a href="http://www.amazon.com/exec/obidos/ASIN/0201700735/classicempire">
491     The C++ Programming Language Special Edition
492     </a><br>
493     Bjarne Stroustrup, Addison-Wesley
494     <p>
495
496 )
497
498 Macros:
499     TITLE=D's Contract Programming vs C++'s
500     WIKI=CppDbc
Note: See TracBrowser for help on using the browser.