FAQ 16.12 What is the right strategy for implementing a function that needs to maintain state between calls?

Turn the function into a functionoid. Do not create a function with local static data.

In C, it was common to create a function that maintained state between calls by means of local, static data inside the function body. Since this is unsafe in a multithreaded environment, in C++ such a function should be implemented as a functionoid, which is a fancy name for a class that has one major member function. The local static data from the original C-like function should become nonstatic member data of the functionoid class. The benefit is to allow different callers to have different values for the datum that used to be static. For example, every calling function that wants its own copy of the function's state can simply create its own distinct functionoid object.

Viewing a function with local static data as a global functionoid object makes it clear why the static data is expensive to model (global variables aren't fun!). For example, consider the rand() function, which remembers some state between calls:

 int rand() throw() {   static unsigned long current = 1001;               <-- 1   current = current * 22695477UL + 1;   return int(current >> 12) & 0x7fff; } 

(1) Bad form: Local static data

The static variable current introduces subtle dependencies between users of the function. Any change in the calling pattern can alter the behavior of this routine. Such routines are notorious in shared-memory, multithreaded environments.

A better way to do this is with a class. Every user function that wants a pseudorandom stream of numbers can create its own object of this class.

 #include <iostream> using namespace std; class RandomSequence {                               <-- 1 public:   RandomSequence(int initialSeed=1001) throw();   int next() throw(); protected:   unsigned long current_; }; RandomSequence::RandomSequence(int initialSeed) throw() : current_(initialSeed) { } int RandomSequence::next() throw() {   current_ = current_ * 22695477UL + 1;   return int(current_ >> 12) & 0x7fff; } 

(1) Good form: Functionoid

The user gets a sequence of random numbers by using the member function next().

 void printRandomSequence() throw() {   RandomSequence rand;   for (int i = 0; i < 10; ++i)     cout << rand.next() << ' '; } int main() { printRandomSequence(); } 

Before, there was a global rand() function with a single state variable. Now there is a local rand object and as many state variables as there are user functions that want an independent pseudorandom sequence. The dependencies among callers (and especially among the various threads) are eliminated at the source. There is no more shared static data.

Another reason to create a functionoid object is when a function performs several distinct operations. In C, such a function would often accept a what-to-do parameter that selected the operation to be performed. In C++, such a multioperation function should be implemented as an object. Each distinct operation performed by the original function should become a distinct member function on the object. Such an object is also called a functionoid.



C++ FAQs
C Programming FAQs: Frequently Asked Questions
ISBN: 0201845199
EAN: 2147483647
Year: 2005
Pages: 566
Authors: Steve Summit

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net