Uses of Retained Visibility

Translation. Suppose you want to support the internationalization of an application. You would like to take every string S in the application and replace it with t(S), where the t() function translates the text into the localized language. To keep away from global data, t() would have to be called with something like t(lang,S). Making sure every part of the application had access to a local version of lang would be ridiculously complex. So most applications resort to a global language setting (which then are a problem if some text should be English and some Chinese). With retained visibilities, the application can create a default translation, and then retain others as needed. While a single global function t() can correctly and safely translate into multiple languages from multiple threads of the application.

Logging. There is always the need to log information. This is usually done with global variables to access the log file, but adjusting/splitting and stopping log files (or other types of output) is often not conveniently a global decision for an application. Retained values makes this much easier to deal with. The logging example in the aforementioned link shows this. It also illustrates using retain-if (if a log file is successfully opened) and is an example of deepening to support multiple logging channels simultaneously.

Footnotes. Retained values do not have to be read-only, so later frames can use retained values to return values higher up the call stack, again without affecting the intervening implementations. Rather than force a channel of information through the intervening code, this can be more "if you are interested" information.

Building. You want to modify/build an object. Always referring to the target object is tedious and prevents some coding simplifications. By retaining a reference to the target object, operations can recall the correct object without always using an explicit reference.

Pooling. You want objects in a pool to be destroyed along with the pool. You can do this by registering the objects with a pool, but this can be cumbersome and fail if you forget to register an object. You can override the new operator so that objects are automatically registered in the currently recalled pool, and thus automatically cleaned up.

Reusing. You have an old library that works. But now some of the callbacks the library require unforeseen information before it can work for a current problem (like a character encoding or database connection). You can retain information before calling the unmodified library, so that the callbacks can recall the information as needed. See sort, oldsort, and newsort at the aforementioned link for an example of this, which also illustrates the use of polymorphism with retained values.

Checkpoints. You are modifying a structure you are not sure you are committed to. By retaining checkpoints, you can automatically undo changes, or accept them and pass undo information to the containing checkpoint. See checkpoint in the aforementioned link for an example of this, which also illustrates employing extra steps when forgetting a retained value.

Problems with Retained Visibility

An important difficulty with retained values is the inability to automatically refer to values between different threads of an application. The programmer must explicitly retain values for a new thread. This is analogous to the throw/catch clauses, as exceptions do not typically propagate to the parent thread for the same reasons.

Another possible issue is that retaining values in a channel of a generic type risks that the value is accidentally recalled in the wrong context. This is analogous to throws of basic types, which are rarely useful except as an illustration. Generally speaking, each recall should be considered for its purpose, not just its data content, and a type made to suit this purpose. For example, even if a midi channel error code might just be an integer, it would be a bad practice to simply retain/recall an integer to store such a code, as it might be accidentally recalled for many other reasons. The iterative deepening semantics allow for searching for a matching retain; but again, if the type is very generic, there is no way to distinguish it at recall. Instead, a specific type (something like midi_channel_error_code_type) should be the type, even if it just contains an integer, so that this value is not confused for some other purpose.

Using the Reference C++ Posix/Win32 Implementation

Here are how the semantics are implemented in the API (after including the retain.hpp template file):

Retain a value for a type:

// set retain for type as value
auto retain<type> as(&value);

The implementation declares a retain<type> object named as as a local value. as is the retain object and typically does not need to be referred to directly. The auto keyword emphasizes that this should only be declared as a local (automatic) instance.

Forget a value for a type:

This is implicit in the C++ implementation when the retain<type> destructor is invoked. Additional actions may be invoked at the point of forgetting by subclassing the retain template class.

The second argument to the retain<type> constructor is an optional Boolean with a default value of true. If the argument is false, this retain instance is inactive, so that retained, recall, and deepening recalls ignore this instance of retain.

I recommend that subclasses of the retain template also have an optional boolean last value to constructors (with default value set to true), so that they may support the retain-if semantics. Note also that, even if the condition is false, the destructor is still invoked. A subclass may choose to store the value of condition in order to properly destroy an unused instance.

Recall a value retained for type:

type *value = recall<type>();

This is a narrow recall, in the sense that only a retain in the ancestral frames for the same type will match this recall.

Here is an example that illustrates a basic use of these semantics, where use_midi() needs a channel id and sets an error code that is not part of the arguments to the function, but is instead accessed through retain/recall:

If there are additional steps to be taken when retaining or forgetting a value, then you can instead subclass retain and define the additional logic in the constructor or destructor, respectively.

When subclassing a retain, it is a good habit to have an optional Bolean argument that defaults to true, and pass it as the last argument of the retain base class constructor. This allows your specialized retains to also be optionally used (so it supports the retain-if semantics). For example:

See the midi2, logging, and checkpoint examples of subclassing in the aforementioned code repository for examples.

Conclusion

The concept of retained visibility is an important expansion of the idea of data visibility in application development. Allowing the text of one frame to recall the retained data of ancestor frames can be used to simplify program development and maintenance without harming efficiency, thread safety, re-entrance, or recurrence. It would be valuable to adapt languages to intrinsically support the concept.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!