1 /** |
|
2 @mainpage |
|
3 |
|
4 @par Overview |
|
5 |
|
6 @par |
|
7 |
|
8 This document describes new core APIs for semi-automatic resource |
|
9 management designed to begin to increase the baseline usability of |
|
10 Symbian OS for developers. We would welcome feedback, improvement |
|
11 suggestions, and defect reports. Note that there is a Q&A section at |
|
12 the bottom of this page that explains some of the background rationale |
|
13 behind the design choices we have made to get to this point. |
|
14 |
|
15 Please send any questions or review feedback you have to: core.idioms@symbian.com |
|
16 |
|
17 The new APIs include the following: |
|
18 |
|
19 - A new type category: the L-class |
|
20 - Allows leaving constructors and can rely on automatic destructor execution for |
|
21 cleanup (explained in more detail below) |
|
22 - New general purpose, self-managing string classes that can be used much |
|
23 like T-classes |
|
24 - LString16 |
|
25 - For manipulating strings of 16 bit characters |
|
26 - Usually used via the ::LString typedef |
|
27 - LString8 |
|
28 - For manipulating strings of 8 bit characters |
|
29 - ::LData |
|
30 - Type alias for LString8 allowing the intent to use it as a raw (non-character) data buffer to be expressed |
|
31 - The LString classes are based on the descriptor APIs and are fully interoperable with descriptors |
|
32 - The LString classes can grow on demand and do not always need to be |
|
33 initialized with a fixed reserve maximum length |
|
34 - New "smart pointer" style utility classes for managing resources referenced from local variables, |
|
35 which may be used instead of the existing cleanup stack API |
|
36 - LCleanedupPtr |
|
37 - Alternative to PushL(CBase*)/Pop()/PopAndDestroy() code sequences |
|
38 - LCleanedupHandle |
|
39 - Alternative to CleanupClosePushL()/Pop()/PopAndDestroy() code sequences |
|
40 - See also the more rarely used LCleanedupRef, LCleanedupArray, and LCleanedupGuard |
|
41 - New "smart pointer" style utility classes for managing resources referenced from data members, |
|
42 which reduce the need for manually-written destructors |
|
43 - LManagedPtr |
|
44 - Alternative to calling delete manually in a destructor for the managed data member |
|
45 - LManagedHandle |
|
46 - Alternative to calling Close() manually in a destructor for the managed data member |
|
47 - See also the more rarely used LManagedRef, LManagedArray, and LManagedGuard |
|
48 - Enablers for the (optional, typically private) use of single-phase construction |
|
49 - #CONSTRUCTORS_MAY_LEAVE |
|
50 - A class declaration flagging that the class's constructors may leave |
|
51 - Normally used in combination with the LManagedXxx classes to ensure reliable cleanup in case of a constructor leaving |
|
52 - Normally hidden behind a conventional Symbian NewL() abstraction |
|
53 - New utilities for making correct, error checking code easier to read |
|
54 - New #OR_LEAVE postfix macro |
|
55 - Alternative to wrapping User::LeaveIfError() around an expression |
|
56 |
|
57 The motivation for the new APIs is to make it easier to write correct, |
|
58 less brittle code in the first instance, but perhaps more so to make |
|
59 the resulting code easier to read and easier to modify safely later. |
|
60 |
|
61 Some guiding principles: |
|
62 |
|
63 - Resource management should be handled either fully automatically or declaratively |
|
64 at point of definition as far as possible |
|
65 - Helps to eliminate mismatches between code that pushes and code that pops |
|
66 - Helps to eliminate mismatches between code that constructs and code that destructs |
|
67 - Removes the need to duplicate cleanup code (or modify your code's structure) to correctly handle functions with multiple return points |
|
68 - Makes changing code safer and easier by reducing dependencies between different areas of the code |
|
69 - It should be easier and lighter weight to write and maintain leave-safe code |
|
70 - So that more people are prepared to make the investment to do it right |
|
71 - The primary function of code should not be swamped by auxiliary error checking and resource management |
|
72 - The key logic should be front and centre, and read as straightforwardly as possible |
|
73 - It should be easier to write fully general code |
|
74 - Unsafe and/or overly conservative maximum size limitations should not be |
|
75 encouraged by making that style of code easier to write |
|
76 - Terminology should be as conventional as possible |
|
77 - We call a string a string |
|
78 |
|
79 To help illustrate the new APIs, here is a sampling of idiomatic code |
|
80 implemented first in terms of the current Symbian APIs, and alongside |
|
81 in terms of the new APIs. |
|
82 |
|
83 <table width="100%"> |
|
84 |
|
85 <tr valign="top"> |
|
86 <td>Current APIs</td><td>New APIs</td> |
|
87 </tr> |
|
88 |
|
89 <tr valign="top"> |
|
90 <td> |
|
91 @code |
|
92 { |
|
93 TBuf<KMaxQuery> query; // fixed worst-case max |
|
94 query.Format(KQueryFormat, param); |
|
95 ExecuteQueryL(query); |
|
96 } |
|
97 @endcode |
|
98 </td> |
|
99 <td> |
|
100 @code |
|
101 { |
|
102 LString query; // can grow its heap buffer on demand |
|
103 query.FormatL(KQueryFormat, param); |
|
104 ExecuteQueryL(query); |
|
105 } // query buffer released on normal scope exit or leave |
|
106 @endcode |
|
107 </td> |
|
108 </tr> |
|
109 |
|
110 <tr valign="top"> |
|
111 <td> |
|
112 @code |
|
113 { |
|
114 HBufC* queryc = HBufC::NewLC(KTooBigForStackMaxQuery); |
|
115 TPtr query(queryc->Des()); |
|
116 BuildQueryL(query); |
|
117 ExecuteQueryL(query); |
|
118 CleanupStack::PopAndDestroy(); |
|
119 } |
|
120 @endcode |
|
121 </td> |
|
122 <td> |
|
123 @code |
|
124 { |
|
125 LString query; |
|
126 BuildQueryL(query); |
|
127 ExecuteQueryL(query); |
|
128 } |
|
129 @endcode |
|
130 </td> |
|
131 </tr> |
|
132 |
|
133 <tr valign="top"> |
|
134 <td> |
|
135 @code |
|
136 { |
|
137 RBuf query; |
|
138 query.CleanupClosePushL(); |
|
139 query.CreateL(TooBigForStackMaxQuery); |
|
140 BuildQueryL(query); |
|
141 ExecuteQueryL(query); |
|
142 CleanupStack::PopAndDestroy(); |
|
143 } |
|
144 @endcode |
|
145 </td> |
|
146 <td> |
|
147 @code |
|
148 { |
|
149 LString query; |
|
150 BuildQueryL(query); |
|
151 ExecuteQueryL(query); |
|
152 } |
|
153 @endcode |
|
154 </td> |
|
155 </tr> |
|
156 |
|
157 <tr valign="top"> |
|
158 <td> |
|
159 |
|
160 @code |
|
161 { |
|
162 CQuery* query = CQuery::NewL(); |
|
163 CleanupStack::PushL(query); |
|
164 query->BuildQueryL(); |
|
165 query->ExecuteQueryL(); |
|
166 CleanupStack::PopAndDestroy(); |
|
167 } |
|
168 @endcode |
|
169 |
|
170 </td> |
|
171 <td> |
|
172 |
|
173 @code |
|
174 { |
|
175 LCleanedupPtr<CQuery> query(CQuery::NewL()); |
|
176 query->BuildQueryL(); |
|
177 query->ExecuteQueryL(); |
|
178 } // query deleted on normal scope exit or leave |
|
179 @endcode |
|
180 |
|
181 </td> |
|
182 </tr> |
|
183 |
|
184 <tr valign="top"> |
|
185 <td> |
|
186 |
|
187 @code |
|
188 { |
|
189 CQuery* query = CQuery::NewL(); |
|
190 CleanupStack::PushL(query); |
|
191 query->BuildQueryL(); |
|
192 CleanupStack::Pop(); |
|
193 return query; |
|
194 } |
|
195 @endcode |
|
196 |
|
197 </td> |
|
198 <td> |
|
199 |
|
200 @code |
|
201 { |
|
202 LCleanedupPtr<CQuery> query(CQuery::NewL()); |
|
203 query->BuildQueryL(); |
|
204 return query.Unmanage(); |
|
205 // query was protected until Unmanage() was called |
|
206 } |
|
207 @endcode |
|
208 |
|
209 </td> |
|
210 </tr> |
|
211 |
|
212 <tr valign="top"> |
|
213 <td> |
|
214 |
|
215 @code |
|
216 { |
|
217 RQuery query; |
|
218 CleanupClosePushL(query); |
|
219 query.BuildQueryL(); |
|
220 query.ExecuteQueryL(); |
|
221 CleanupStack::PopAndDestroy(); |
|
222 } |
|
223 @endcode |
|
224 |
|
225 </td> |
|
226 <td> |
|
227 |
|
228 @code |
|
229 { |
|
230 LCleanedupHandle<RQuery> query; |
|
231 query->BuildQueryL(); |
|
232 query->ExecuteQueryL(); |
|
233 } // query is closed on normal scope exit or leave |
|
234 @endcode |
|
235 |
|
236 </td> |
|
237 </tr> |
|
238 |
|
239 </table> |
|
240 |
|
241 @par Concepts |
|
242 |
|
243 These APIs introduce a new category of Symbian type: the |
|
244 L-class. L-classes have characteristics close to standard C++ value |
|
245 and handle classes, including the following: |
|
246 |
|
247 - Constructors, operators, and implicit operations may leave |
|
248 - They are self-managing and do not require auxiliary memory management logic |
|
249 |
|
250 L-classes may be used like T-classes except that they can own |
|
251 resources, they typically rely on guaranteed execution of their |
|
252 destructors, and they cannot necessarily be naively bitwise |
|
253 copied. Any code that manipulates L-class instances must be |
|
254 leave-safe. The only exception to this rule is if the particular |
|
255 operations being used are explicitly documented not to leave (e.g. the |
|
256 default LString constructor and all the LManagedXXX constructors). |
|
257 |
|
258 The L prefix denotes that these classes may be Liberal when it comes |
|
259 to Leaving, while being Leave-safe themselves. |
|
260 |
|
261 The implementation of many L classes relies on the leave=throw mapping |
|
262 current in Symbian OS since v9.1 because this guarantees destructor |
|
263 execution for objects allocated on the stack. |
|
264 |
|
265 @par Notes on Usage |
|
266 |
|
267 For ease of experimentation during review, all the new APIs are |
|
268 packaged for use as a statically-linked bundle: |
|
269 |
|
270 - #include <euserhl.h> in your code |
|
271 - STATICLIBRARY euserhl.lib in your mmp |
|
272 |
|
273 Some important dos and don'ts (most of which leavescan will reliably flag): |
|
274 |
|
275 - The LCleanedupXXX classes are for managing locals only, and may not be used to manage data members. This is because the LCleanedupXXX classes are implemented in terms of the legacy cleanup stack. |
|
276 - The LCleanedupXXX classes must not be used in the same function as the legacy cleanup stack APIs. They can interact unintuitively and this is best avoided. It is all or nothing at function granularity. |
|
277 - Use L-suffix method variants with LCleanedupXXX, never LC-suffix variants that manipulate the cleanup stack. This is for the same reason as the previous point. |
|
278 - The LManagedXXX classes should only be used for data members. If they are used for locals, they can interact unintuitively with cleanup stack based cleanup (from other functions on the call stack) with respect to cleanup ordering. |
|
279 - Whenever you write code that manipulates LString or other L-prefix classes, always conservatively leave-safe your code. This is now easier with the LCleanedupXXX utilities. |
|
280 |
|
281 On the resource management utility classes: |
|
282 |
|
283 - A managed object's member functions may be called directly on the managing container, but -> notation must be used to access them |
|
284 - Implicit coercions from managing containers to managed types are not defined; instead the dereference operator * must be used e.g. to obtain a reference suitable for passing as a parameter to another function |
|
285 - Pop()-like semantics (as opposed to PopAndDestroy()) is achieved by calling Unmanage() on the managing container before it goes out of scope; Unmanage() returns the managed object which can then be returned from your function, or passed to a different function which takes over ownership. Unlike Pop(), however, Unmanage() simply sets a flag to disable cleanup and may be called at any time without worrying about stack discipline |
|
286 - It is still typically necessary to declare, define, and export a destructor, even if its implementation (currently) contains no hand-written code |
|
287 - The destructor must be available wherever delete may be called on an instance of the class |
|
288 - The compiler will automatically generate destruction logic behind the scenes for LString and LManagedXxx data members, even if you don't write any code yourself |
|
289 - The destructor may of course change over time anyway |
|
290 |
|
291 On the LString classes: |
|
292 |
|
293 - Existing library functions that accept a modifiable TDes cannot take advantage of LString's on-demand growth behaviour; the necessary capacity must first be explicitly reserved in the LString before calling such functions (this can be done either with a constructor parameter or by calling SetMaxLengthL()) |
|
294 - Any descriptor method that could panic due to lack of capacity is replaced with a leaving variant that grows the heap e.g. instead of Append(), LString defines AppendL(). The non-leaving TDes versions are hidden to avoid accidental use, although they can still be accessed via a TDes typed (as opposed to LString typed) ref or pointer |
|
295 - Programmers new to Symbian OS could first be introduced to descriptors and string handling via just const TDesC& (for input parameter contracts), TDes (for output parameter contracts), and LString for implementation. LStrings can be initialized by value from any descriptor return type, and by automatically taking ownership in the HBufC* return case. |
|
296 |
|
297 On single-phase construction: |
|
298 |
|
299 - This option is enabled by LString and the LManagedXxx classes |
|
300 - However you implement construction behind the scenes, NewL is an important abstraction |
|
301 - Think hard before using single-phase construction in a framework class designed for derivation in client code |
|
302 - LString and the LManagedXxx classes may be used safely in conventional Symbian two-phase construction as well as standard C++ style single-phase construction |
|
303 - LManagedXxx construction never leaves, and is safe to perform at any stage |
|
304 - Default construction of an empty LString never leaves, but other initializations must be deferred to ConstructL |
|
305 - An understanding of standard C++ construction semantics is required before attempting to use single-phase construction |
|
306 - If a constructor throws mid-way through, its matching destructor is not invoked to clean up |
|
307 - Instead all data members fully-constructed to the point of the leave are automatically destructed |
|
308 - Therefore, in effect, all class cleanup must typically be achieved via data member destruction |
|
309 - The presence of a non-empty destructor is a warning sign if you are using single-phase construction |
|
310 |
|
311 An annotated example using the new APIs follows (the |
|
312 euserhl_walkthrough example goes into much more detail than we do |
|
313 here). |
|
314 |
|
315 @code |
|
316 #include <e32std.h> |
|
317 #include <f32file.h> |
|
318 #include <euserhl.h> // Includes all the new APIs |
|
319 |
|
320 class CFinder : public CBase |
|
321 { |
|
322 public: |
|
323 // We have opted to use single-phase construction here, and some of |
|
324 // our constructor's initialization actions may leave. In order to |
|
325 // guarantee full cleanup in all cases, we have to declare this fact. |
|
326 CONSTRUCTORS_MAY_LEAVE |
|
327 static CFinder* NewL(const TDesC& aPattern); |
|
328 ~CFinder(); |
|
329 void GetNextMatchL(TDes& aMatch); |
|
330 |
|
331 protected: |
|
332 CFinder(const TDesC& aPattern); |
|
333 |
|
334 protected: |
|
335 LString iPattern; |
|
336 LManagedHandle<RFs> iFs; // We always use LManagedXxx for data members |
|
337 // ... |
|
338 }; |
|
339 |
|
340 CFinder* CFinder::NewL(const TDesC& aPattern) |
|
341 { |
|
342 return new(ELeave) CFinder(aPattern); |
|
343 } |
|
344 |
|
345 CFinder::CFinder(const TDesC& aPattern) |
|
346 // This initializer may leave, since the LString will allocate a |
|
347 // heap buffer large enough to contain a copy of aPattern's data |
|
348 : iPattern(aPattern) |
|
349 { |
|
350 // If connection fails and we leave here, iPattern's destructor |
|
351 // will be called automatically, and the string's resources will |
|
352 // be released |
|
353 iFs->Connect() OR_LEAVE; |
|
354 } |
|
355 |
|
356 CFinder::~CFinder() |
|
357 { |
|
358 // Automatic destruction of each of the data members does all of the |
|
359 // work for us: iPattern's heap buffer if freed, while Close() is |
|
360 // called on the managed RFs in iFs. |
|
361 |
|
362 // Even though this destructor is textually empty, it should |
|
363 // still be exported; the compiler is generating destruction logic |
|
364 // for us in this case |
|
365 } |
|
366 |
|
367 void CFinder::GetNextMatchL(TDes& aMatch) |
|
368 { |
|
369 // ... |
|
370 LString possible; |
|
371 // ... |
|
372 LCleanedupHandle<RFile> file; // We always use CleanedupXXX for locals |
|
373 TInt status = file->Open(*iFs, possible, EFileRead); // Note use of -> and * here |
|
374 // ... |
|
375 aMatch = possible; |
|
376 // ... |
|
377 } |
|
378 @endcode |
|
379 |
|
380 For comparison, the original code in terms of existing APIs: |
|
381 |
|
382 @code |
|
383 #include <e32std.h> |
|
384 #include <f32file.h> |
|
385 |
|
386 class CFinder : public CBase |
|
387 { |
|
388 public: |
|
389 static CFinder* NewL(const TDesC& aPattern); |
|
390 ~CFinder(); |
|
391 void GetNextMatchL(TDes& aMatch); |
|
392 |
|
393 protected: |
|
394 CFinder(); |
|
395 void ConstructL(const TDesC& aPattern); |
|
396 |
|
397 protected: |
|
398 HBufC* iPattern; |
|
399 RFs iFs; |
|
400 // ... |
|
401 }; |
|
402 |
|
403 CFinder* CFinder::NewL(const TDesC& aPattern) |
|
404 { |
|
405 CFinder* self = new(ELeave) CFinder; |
|
406 CleanupStack::PushL(self); |
|
407 self->ConstructL(aPattern); |
|
408 CleanupStack::Pop(); |
|
409 return self; |
|
410 } |
|
411 |
|
412 CFinder::CFinder() |
|
413 { |
|
414 } |
|
415 |
|
416 void CFinder::ConstructL(const TDesC& aPattern) |
|
417 { |
|
418 iPattern = aPattern.AllocL(); |
|
419 User::LeaveIfError(iFs.Connect()); |
|
420 } |
|
421 |
|
422 CFinder::~CFinder() |
|
423 { |
|
424 iFs.Close(); |
|
425 delete iPattern; |
|
426 } |
|
427 |
|
428 void CFinder::GetNextMatchL(TDes& aMatch) |
|
429 { |
|
430 // ... |
|
431 RFile file; |
|
432 CleanupClosePushL(file); |
|
433 TInt status = file.Open(iFs, possible, EFileRead); |
|
434 // ... |
|
435 aMatch = possible; |
|
436 // ... |
|
437 CleanupStack::PopAndDestroy(); |
|
438 } |
|
439 @endcode |
|
440 |
|
441 @par Q&A |
|
442 |
|
443 \b General.1 <em>Why start at the bottom? Shouldn't improving the usability of Symbian |
|
444 OS and its APIs be a top-down initiative?</em> |
|
445 |
|
446 There is a product-wide usability initiative within Symbian which aims |
|
447 to address all of APIs, documentation, and tools. On the API side, |
|
448 there are tracks that try to improve the design process for good API |
|
449 usability going forward, and tracks like this one that try to improve |
|
450 the usability of existing functionality. This is just the first |
|
451 deliverable of the latter effort, but other work is ongoing. |
|
452 |
|
453 \b General.2 <em>Will Core Idioms changes like these be backed up by tools support?</em> |
|
454 |
|
455 Yes, support for tools like Leavescan and Coverity, as well as more |
|
456 basic debugger presentation support for the new descriptor types and |
|
457 resource management classes, is being planned into the roll-out |
|
458 project. |
|
459 |
|
460 \b General.3 <em>Will Core Idioms changes like these be backed up by matching |
|
461 documentation, examples, and coding standards updates?</em> |
|
462 |
|
463 Yes, we understand this to be essential and this is our goal. |
|
464 |
|
465 \b General.4 <em>Symbian will support STL as part of Open Environment, and |
|
466 Symbian's STL implementation includes std::string and |
|
467 std::auto_ptr. Why not just use them?</em> |
|
468 |
|
469 We investigated this option to the point of creating a native Symbian |
|
470 C++ interoperable STL subset for experimentation. This was ultimately |
|
471 rejected for the following reasons: |
|
472 |
|
473 - Hybrid-style code (with mixed Symbian-style and standard-style naming) is |
|
474 harder to read and to work with than code in a single consistent style |
|
475 - Beyond basic naming conventions, supporting two very different APIs |
|
476 for manipulating strings (the descriptor methods alongside the |
|
477 std::string methods) similarly does not help usability |
|
478 - Interoperability between std::string and TDesC/TDes accepting APIs |
|
479 implemented via implicit coercion is more limited than the direct placement |
|
480 of new string classes within the existing descriptor hierarchy can |
|
481 achieve |
|
482 - Because we need a number of Symbian-specific resource management |
|
483 utilities (cleanup-stack aware, R-class aware, and so on), auto_ptr |
|
484 on its own does not provide much leverage |
|
485 - It would require maintaining two subtly variant STL builds: the Open |
|
486 Environment version with standard C++ new and exception |
|
487 behaviour, and a Symbian C++ version with new(ELeave) and leaving |
|
488 behaviour |
|
489 |
|
490 Instead we opted to give Symbian C++ a "programming philosophy |
|
491 upgrade"; more aligned with standard C++ but still in the style of |
|
492 Symbian C++ and its existing APIs. The result is something that can be |
|
493 more easily and more incrementally adopted by existing Symbian C++ |
|
494 projects and teams. Note that this decision to focus first on building |
|
495 out the usability of native Symbian C++ does not preclude the |
|
496 possibility of an official hybridized Symbian embedding of STL being |
|
497 developed at a later date |
|
498 |
|
499 Finally, we could have used Symbian-ized names like XAutoPtrYyy as the |
|
500 basis for the resource management utility classes. However, this was |
|
501 deliberately avoided because the semantics of our classes are |
|
502 different from auto_ptr e.g. they can't support copy construction or |
|
503 transfer of ownership through parameter passing and return, and |
|
504 different variants are required for use with locals than with data |
|
505 members. We did not want to give the Symbian classes names that could |
|
506 result in developers making natural but incorrect assumptions about |
|
507 their behaviour based experience with similarly-named classes |
|
508 elsewhere. |
|
509 |
|
510 \b General.5 <em>These new template classes seem more conceptually |
|
511 complex than what they're replacing. How can this improve |
|
512 usability?</em> |
|
513 |
|
514 Template classes can be complex to implement, but they're not |
|
515 particularly difficult to understand or use in the way they're |
|
516 employed here. Symbian programmers are already familiar with the |
|
517 basics from RArray<T> and RPointerArray<T>. And to anyone coming from |
|
518 standard C++ and STL it's second nature, as in fact is this general |
|
519 style of parameterised declaration to desktop Java and C# programmers |
|
520 used to generics. |
|
521 |
|
522 In the end, it's just a concise, declarative annotation that takes |
|
523 leaky code and helps turn it into robust code. |
|
524 |
|
525 Leaky: |
|
526 |
|
527 @code |
|
528 { |
|
529 CFoo* foo = CFoo::NewL(); |
|
530 foo->ExecuteL(); // the CFoo leaks if this leaves |
|
531 if (foo->IsAsync()) |
|
532 { |
|
533 // ... |
|
534 return; // the CFoo leaks if we return |
|
535 } |
|
536 // ... |
|
537 } // the CFoo leaks if we exit scope normally |
|
538 @endcode |
|
539 |
|
540 Safe (new style): |
|
541 |
|
542 @code |
|
543 { |
|
544 LCleanedupPtr<CFoo> foo(CFoo::NewL()); |
|
545 foo->ExecuteL(); // the CFoo is automatically deleted if we leave |
|
546 if (foo->IsAsync()) |
|
547 { |
|
548 // ... |
|
549 return; // the CFoo is automatically deleted if we return |
|
550 } |
|
551 // ... |
|
552 } // the CFoo is automatically deleted if we exit scope normally |
|
553 @endcode |
|
554 |
|
555 Safe (classic style): |
|
556 |
|
557 @code |
|
558 { |
|
559 CFoo* foo = CFoo::NewL(); |
|
560 CleanupStack::PushL(foo); |
|
561 foo->ExecuteL(); // the CFoo is automatically deleted if we leave |
|
562 if (foo->IsAsync()) |
|
563 { |
|
564 // ... |
|
565 CleanupStack::PopAndDestroy(foo); // the CFoo is deleted manually |
|
566 return; |
|
567 } |
|
568 // ... |
|
569 CleanupStack::PopAndDestroy(foo); // the CFoo is deleted manually |
|
570 } |
|
571 @endcode |
|
572 |
|
573 \b General.6 <em>Don't some of these APIs simply trade depth for width? There are less lines of code but the statements are longer and more complex</em> |
|
574 |
|
575 Placing as much information at point of declaration as possible guards |
|
576 against separate lines of code becoming inconsistent with one-another, |
|
577 particularly over time in the face of later code changes. Where |
|
578 cleanup code is duplicated, for example in the case of a function with |
|
579 multiple exit points, or where the cleanup point is separated by tens |
|
580 of lines of code from the creation point, these risks increase |
|
581 further. |
|
582 |
|
583 Another way to look at this is that it is far easier in a real, |
|
584 non-trivial piece of code to take an unprotected local (a raw pointer |
|
585 to an object) and to apply cleanup protection to it (for example if a |
|
586 call to a leaving function has been added) once at its point of |
|
587 creation, than it is to study what might be a quite complex flow of |
|
588 control in order to determine where all the matching calls to |
|
589 CleanupStack::PopAndDestroy() need to go. |
|
590 |
|
591 In some cases, faced with the CleanupStack, you might actually choose |
|
592 to rework an existing function to have a single exit point to simplify |
|
593 the process of leave-protecting its code. The risks inherent in that |
|
594 are not necessary using the mode of protection offered by these new |
|
595 APIs. |
|
596 |
|
597 \b General.7 <em>These APIs seem to come with a lot of new rules, |
|
598 particularly when it comes to interoperating with existing |
|
599 functionality. If it turns out people need to understand both the new |
|
600 and old idioms in order to mix them safely, where's the benefit? |
|
601 Doesn't it actually make things worse?</em> |
|
602 |
|
603 The interoperability constraints are far from ideal, but we believe |
|
604 they are manageable and that the benefits outweigh the issues, |
|
605 particularly so for developers working on new code that uses these new |
|
606 APIs and idioms consistently. |
|
607 |
|
608 Wherever possible sanity checking will be offered in code analysis |
|
609 tools like Leavescan and/or in UDEB runtime checking via assertions. |
|
610 |
|
611 \b Cleanedup.1 <em>The LCleanedupXxx/LManagedXxx distinction is confusing. Why can't we |
|
612 just use the same classes everywhere?</em> |
|
613 |
|
614 We would much prefer to be able to use the same classes to protect |
|
615 objects referenced from local variables as we do to project objects |
|
616 referenced from data members. In an ideal world, the LManagedXxx |
|
617 classes would be used in both scenarios (just like auto_ptr is |
|
618 elsewhere); LCleanedupXxx would not be necessary. |
|
619 |
|
620 In fact, the LManagedXxx classes can be used to protect locals if you |
|
621 wish: |
|
622 |
|
623 @code |
|
624 { |
|
625 LManagedPtr<CQuery> query(CQuery::NewL()); |
|
626 query->BuildQueryL(); |
|
627 query->ExecuteQueryL(); |
|
628 } // query deleted on normal scope exit or leave |
|
629 @endcode |
|
630 |
|
631 This works fine in isolation. Unfortunately, cleanup order becomes |
|
632 counter-intuitive if calls to functions that use LManagedXxx are mixed |
|
633 with functions that use the cleanup stack with the same call stack. |
|
634 |
|
635 Here is an example. Call order is top-to-bottom. |
|
636 |
|
637 @code |
|
638 void CFramework::StartMainLoopL() |
|
639 { |
|
640 CFramework* fw = CFramework::NewL(); |
|
641 CleanupStack::PushL(fw); // #1 |
|
642 // ... |
|
643 CleanupStack::PushL(event); // #2 |
|
644 fw->DispatchL(*event); |
|
645 CleanupStack::PopAndDestroy(event); |
|
646 // ... |
|
647 CleanupStack::PopAndDestroy(fw); |
|
648 } |
|
649 |
|
650 void CFramework::DispatchL(CEvent& aEvent) |
|
651 { |
|
652 this->UserCallback()(*this, aEvent); |
|
653 } |
|
654 |
|
655 void MyUserCallback(CFramework& aFw, CEvent& aEvent) |
|
656 { |
|
657 LManagedPtr<CMyEventWrapper> myEvent(CMyEventWrapper::NewL(aEvent)); // #3 |
|
658 // ... |
|
659 aFw->LookupL(arg); |
|
660 // ... |
|
661 } |
|
662 |
|
663 void CFramework::LookupL(const TDesC& aArg) |
|
664 { |
|
665 // |
|
666 CleanupStack::PushL(tmp); // #4 |
|
667 // ... |
|
668 User::LeaveIfError(status); |
|
669 // ... |
|
670 CleanupStack::PopAndDestroy(tmp); |
|
671 } |
|
672 @endcode |
|
673 |
|
674 If the User::LeaveIfError() expression triggers an unhandled leave in |
|
675 the above, the cleanup order would be: #4, #2, #1, #3. If out-of-order |
|
676 execution of CMyEventWrapper's destructor can never cause problems, |
|
677 then there is not an issue. However, if CMyEventWrapper's destructor |
|
678 were to e.g. decrement a reference count on the (deleted) CEvent, this |
|
679 would lead to a panic. |
|
680 |
|
681 The cleanup order is this way because of the way User::Leave() |
|
682 processing works. It locates all pushed CleanupStack items up to the |
|
683 point of the handling TRAP and executes their cleanups. Then, finally, |
|
684 it uses C++ throw to unwind the C stack to the point of the TRAP. It |
|
685 is during C stack unwinding that the destructors for objects stored in |
|
686 locals get run, and LManagedPtr (like auto_ptr) relies exclusively on |
|
687 destructor execution to trigger cleanup. |
|
688 |
|
689 On the other hand, if MyUserCallback uses LCleanedupPtr instead of |
|
690 LManagedPtr, then its associated cleanup is triggered as part of |
|
691 CleanupStack processing and in the more intuitive sequence: #4, #3, |
|
692 #2, #1. Note that although it also has a destructor, LCleanedupPtr's |
|
693 cleanup action is only ever run once; execution of the cleanup action |
|
694 via the destructor is disabled if the cleanup action has already been |
|
695 run as part of CleanupStack processing. |
|
696 |
|
697 This is why the LCleanedupXxx classes exist. Because the situations |
|
698 where use of LManagedXxx for locals (or more precisely the resulting |
|
699 out-of-order cleanup) may cause problems are subtle to characterise, |
|
700 we guide conservatively that LCleanedupXxx should always be used to |
|
701 protect locals. |
|
702 |
|
703 \b Cleanedup.2 <em>If you are forced to call an LC method, how should you deal with it?</em> |
|
704 |
|
705 APIs should always offer pure L variants (after all, what if you want |
|
706 to store the result directly in a data member?) and this scenario |
|
707 should be rare. If it does arise, however, you have a few options. |
|
708 |
|
709 Note that code checking tools like Leavescan can flag dangerous |
|
710 combinations of LC method calls and the LCleanedupXxx classes within |
|
711 the same function. |
|
712 |
|
713 Option 1 (preferred): |
|
714 |
|
715 @code |
|
716 // Define your own popping L wrapper function |
|
717 CFoo* NewCFooL(TFooArg aArg) |
|
718 { |
|
719 CFoo* foo = CFoo::NewLC(aArg); |
|
720 CleanupStack::Pop(); |
|
721 return foo; |
|
722 } |
|
723 |
|
724 // Then call that instead... |
|
725 { |
|
726 LCleanedupPtr<CFoo> foo(NewCFooL(arg)); |
|
727 // ... |
|
728 } |
|
729 |
|
730 @endcode |
|
731 |
|
732 Option 2: |
|
733 |
|
734 @code |
|
735 { |
|
736 LCleanedupPtr<CFoo> foo; // pushes our "smart" cleanup item on the stack |
|
737 foo = CFoo::NewLC(arg); // leaves a classic cleanup item on the stack |
|
738 CleanupStack::Pop(); // gets rid of the classic cleanup item right away |
|
739 // ... |
|
740 } |
|
741 @endcode |
|
742 |
|
743 Never do this: |
|
744 |
|
745 @code |
|
746 { |
|
747 LCleanedupPtr<CFoo> foo(CFoo::NewLC(arg)); |
|
748 // Execution order of the above statement leaves our "smart" cleanup item |
|
749 // on top of the cleanup stack and we can't get at the LC-introduced cleanup |
|
750 // item beneath it to pop it |
|
751 // ... |
|
752 } |
|
753 @endcode |
|
754 |
|
755 \b Cleanedup.3 <em>How do you create your own custom cleanup operation a la TCleanupItem?</em> |
|
756 |
|
757 The direct analog to pushing a TCleanupItem is ::LCleanedupGuard; see |
|
758 its documentation for an example of how to register a function pointer |
|
759 with argument data to get invoked on cleanup. |
|
760 |
|
761 See also #DEFINE_CLEANUP_FUNCTION and #DEFINE_CLEANUP_STRATEGY for |
|
762 creating custom cleanup operations for use with particular classes. |
|
763 |
|
764 \b Managed.1 <em>For LManagedXxx, is destruction order based on order of |
|
765 declaration?</em> |
|
766 |
|
767 Yes, fields are destroyed (and so the cleanup operation run in the |
|
768 case of LManagedXxx fields) in reverse order of declaration as |
|
769 specified by the C++ standard. |
|
770 |
|
771 \b Managed.2 <em>Does tying cleanup order to data member order make it |
|
772 harder to maintain binary compatibility? If I want to change |
|
773 construction/destruction order I'm forced to change my header and |
|
774 reorder my data members?</em> |
|
775 |
|
776 Reordering private fields would not constitute a BC break because the |
|
777 class would not change size. In situations where your data members are |
|
778 anything other than private, including being directly revealed by |
|
779 inline methods, you always have to take great care, and will continue |
|
780 to have to do so. |
|
781 |
|
782 As a general guideline, if you are concerned about BC never make data |
|
783 members (LManagedXxx or otherwise) anything other than private. Always |
|
784 expose them via non-inline accessor methods, both to clients and to |
|
785 derived classes. |
|
786 |
|
787 \b Managed.3 <em>What happens when you mix managed and unmanaged |
|
788 pointers in the same class? Should it be avoided?</em> |
|
789 |
|
790 Your manually-written destructor code for a class will get run before |
|
791 the managed fields of that class get cleaned up, as per the C++ |
|
792 standard. As long as this is understood, there is no particular reason |
|
793 to avoid mixing if it makes sense to your problem. |
|
794 |
|
795 Note, though, that if you are using single-phase construction and the |
|
796 constructor leaves, your destructor will not be run; only the |
|
797 instance's fully-constructed data members at the point of the leave |
|
798 will be destroyed. |
|
799 |
|
800 In some cases using ::LManagedGuard may be preferable to adding code |
|
801 to the destructor. |
|
802 |
|
803 \b SinglePhase.1 <em>Do you have to use single-phase construction with |
|
804 these APIs?</em> |
|
805 |
|
806 No. The primary benefit of using the LManagedXxx classes to look after |
|
807 your data members is to avoid having to write and maintain destructor |
|
808 code manually. It's fine to initialize your LManagedXxx fields by |
|
809 assignment in ConstructL if you're using two-phase construction; the |
|
810 automatic cleanup benefit is the same. |
|
811 |
|
812 The LManagedXxx classes also happen to enable the use of single-phase |
|
813 construction, but you need to consider carefully when and whether to |
|
814 use it. |
|
815 |
|
816 \b SinglePhase.2 <em>The single-phase constructor solution with |
|
817 CONSTRUCTORS_MAY_LEAVE is a bit of a mess, and not really helping to |
|
818 simplify code?</em> |
|
819 |
|
820 Yes. Unfortunately the need for #CONSTRUCTORS_MAY_LEAVE is forced by |
|
821 Symbian's legacy handling new(ELeave). |
|
822 |
|
823 \b SinglePhase.3 <em>Why and where would I ever use single-phase |
|
824 construction?</em> |
|
825 |
|
826 For classes not intended for derivation there is no issue, and it's |
|
827 quite reasonable to use the more concise single-phase construction |
|
828 idiom, typically still hidden behind a NewL. |
|
829 |
|
830 For abstract framework classes intended for derivation outside the |
|
831 defining module, the single-phase/two-phase distinction would need to |
|
832 be clearly documented and so would force awareness of at least some of |
|
833 the issues described here, even in scenarios where the deriving client |
|
834 was not intending to use the new idioms. This is probably not |
|
835 desirable. |
|
836 |
|
837 \b OrLeave.1 <em>Can you still use User::LeaveIfError() now that |
|
838 OR_LEAVE is available? What if you want to locally-handle some error |
|
839 cases but not others.</em> |
|
840 |
|
841 Yes, #OR_LEAVE is simply a convenience macro that allows you to |
|
842 deemphasize auxiliary error checking code in most cases. You can still |
|
843 bind an error code manually, check it, and then use |
|
844 User::LeaveIfError(). |
|
845 |
|
846 \b Strings.1 <em>Why derive LString from RBuf and then hide parts of |
|
847 the inherited RBuf/TDes API?</em> |
|
848 |
|
849 We want maximum interoperability with existing code and APIs for |
|
850 LString in order to maximise its usefulness. In order to achieve that |
|
851 we derive from RBuf, allowing an LString instance to be passed |
|
852 directly as a const TDesC, a TDes, or an RBuf. |
|
853 |
|
854 The trade-off is that parts of the RBuf API don't fit well with |
|
855 LString. To be conservative, we have hidden anything inherited from |
|
856 RBuf or TDes that we have found to be confusing to direct users of |
|
857 LString (you can add things later to a new API, but you can less |
|
858 easily take them away). The most obvious of these is the mass hiding |
|
859 of of the non-leaving descriptor methods for which LString provides |
|
860 leaving, auto-growing alternatives: e.g. for direct users of LString, |
|
861 we hide Append() so that it can't be used accidentally instead of |
|
862 AppendL(). |
|
863 |
|
864 Note that when passed as a TDes or RBuf, and viewed through a variable |
|
865 of that type, this localised hiding within LString does not have any |
|
866 effect; existing code handed an LString will be able to manipulate it |
|
867 as it expects to. |
|
868 |
|
869 \b Strings.2 <em>Does LString support TDesC/TDes features like char* |
|
870 interop?</em> |
|
871 |
|
872 LString is an RBuf, a TDes, and a TDesC, and retains all the |
|
873 functionality of those classes. |
|
874 |
|
875 LString8 retains the same level of char* interop as existing |
|
876 descriptor classes through support for initialization from TUint8* |
|
877 zero-terminated C strings, the ZeroTerminateL() utility method, Ptr(), |
|
878 and so on. |
|
879 |
|
880 \b Strings.3 <em>Does LString auto-compress as well as auto-grow?</em> |
|
881 |
|
882 They don't. It can be hard to avoid pathologies when second-guessing |
|
883 when client code if finished with buffer capacity. |
|
884 |
|
885 */ |
|