Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 6.  Scoping Classes


6.4. Language Features

I'm sure you're a bit over scoping classes by now, but I'm going to give you just one last interesting example before we drop the subject.

The C++ language allows you to customize the way in which memory exhaustion is handled, by means of your calling the free function std::set_new_handler() (C++-98: 18.4). You pass a function with the signature defined by the type new_handler, as follows:



typedef void (*new_handler)();



set_new_handler() returns the currently registered handler, or the null pointer if one wasn't previously registered. When operator new() cannot satisfy a request to allocate memory, it calls the new handler if one's been registered, or throws an instance of std::bad_alloc otherwise. The new handler function should either succeed in acquiring more memory to satisfy the request, or should throw an instance of std::bad_alloc[5] itself.

[5] Or a class derived from std::bad_alloc.

By specifying your own handlers, you can customize the behavior of the heap in low memory conditions, or provide extended error responses, such as logging the condition (as long as that action does not itself require allocation from the heap, of course).

The problem here is how to make sure our code behaves consistently. For example, if we wish to plug in a new new-handler, how/where/when do we do it? If we do that in main(), it's quite likely that we'll have missed the execution of a fair amount of code—such as the constructors of globals (global, namespace, or class-static objects; see section 11.1)—that saw a different new-handler. Potentially a memory exhaustion condition could occur and result in behavior that we did not intend, and may not be prepared to handle. But how do we ensure that our new- handler gets plugged in before anything happens? And how do we decide which bit of code has the responsibility of setting the new-handler?

Well, the answer involves scoping classes, of course. For example, the Synesis libraries' root header contains code like that shown in Listing 6.14.

Listing 6.14.


#ifdef __cplusplus





void custom_nh(void); // The customized handler





class NewHandlerInitiator


{


public:


  NewHandlerInitiator()


    : m_oldHandler(set_new_handler(custom_nh))


  {}


  ~NewHandlerInitiator()


  {


    set_new_handler(m_oldHandler);


  }


private:


  new_handler_t   m_oldHandler;


// Not to be implemented


  . . .


};


static NewHandlerInitiator   s_newHandlerInitiator;


  . . .


#endif /* __cplusplus */



NewHandlerInitiator is a scoping class that scopes the setting of a new-handler. In the constructor it sets custom_nh() as the new new-handler, and remembers the previous handler in m_oldHandler. In the destructor it does the courteous thing and switches it back.

The significant part of this is the following line, in which a static instance of the class is defined. This causes s_newHandlerInitiator to have internal linkage[6] (C++-98: 3.5.3; and see sections 11.1.2 and 11.2.3), which means that every compilation unit in which this definition appears will see a unique instance of it.

[6] It uses static rather than an anonymous namespace (C++-98: 7.3.1.1) due to its age, and backward compatibility. This is an anachronism, and use of anonymous namespaces should be preferred [Stro1997], as shown in section 11.1.2.

Since this code is in the root header, every C++ compilation unit that uses the Synesis libraries will contain an instance of the NewHandlerInitiator. Hence, it doesn't matter which one is linked first (and therefore has any globals constructed first), because the s_newHandlerInitiator ensures that the setting of the new-handler is performed. Of course, this doesn't apply to any code that is not created by "us," such as third-party source and/or static libraries. But this doesn't matter, because they're not going to be expecting our special new-handler to activate if they cause memory exhaustion, even if they happen to link first and hence have global/namespace/class-statics constructed before the change of handler.

Of course, all this would be of minor significance if people would avoid globals as we all know that they should, but I leave it to others to write code for an ideal world; we've got to deal with this one.

This technique of declaring static instances in headers is a very powerful technique—open to abuse of course—and we will look at it again in Part Two when we learn just how perilous the use of globals really is.


      Previous section   Next section