Static object initialization w/ inheritence

Started by
6 comments, last by Stoffel 22 years, 4 months ago
Thought I''d share something that I ran across yesterday. I''m trying to write a filter engine that will allow adding filters for new data types but will not force the calling code to recompile. This is based on the "Pluggable Factories" pattern in literature (google if you wanna). The thought was that the filter base class contains a map of all filter objects, mapping the type of data handled to a pointer to the object. I chose to do this in the constructor, and made the filters singletons so that they register themselves with the base class on startup. Now we have the infamous static problem: the construction of any static object across modules is arbitrary. That''s fine for each of the filters, as long as they construct themselves in one thread. The problem is with the base class which manages them--the map of filters is also a static member. I thought to myself, "Well, since the base ctor happens before the derived ctor, its static objects will be constructed first." Nope. Here''s some pseudocode so you can see what I mean:
  
// filtermaker.h

class FilterMaker
{
private:
  typedef map<int, FilterMaker*> FilterMap; // maps data type to

                                            // filter*

  static FilterMap s_handlers;
protected:
  // ctor called by derived classes to register type

  FilterMaker (int type);
  virtual void doFilter (Data &pkt) = 0; // actual filter code

                                         // in derived class

public:
  // function that client will call to filter data

  static void filterData (int type, Data &pkt);
};

// filtermaker.cpp

FilterMaker::FilterMap FilterMaker::s_filters; // static

FilterMaker::FilterMaker (int type)
{
  s_handlers.insert (FilterMap::value_type (type, this));
}
void FilterMaker::filterData (int type, Data &pkt)
{
  // in my code I actually check to see if this filter is

  // registered, but the gist is...

  s_handlers[type]->doFilter (pkt);
}

// ExFilter.h (example filter)

class ExFilter : public FilterMaker
{
protected:
  virtual void doFilter (Data &pkt); // implementation

private:  
  ExFilter (); // only I get to make one..

  static ExFilter s_instance; // ..and that''s it

};
// ExFilter.cpp

ExFilter ExFilter::s_instance; // static

  
Hope that''s clear. Well, the problem cam in right at the start of the program. ExFilter::s_instance started making itself, as planned, and then called the FilterMaker base-class ctor. However, when it came time to insert data in the map, the map constructor had not yet been called! s_filters was uninitialized. I had thought that the base-then-derived construction would insinuate that static members of the base class would be initialized before static members of derived classes. Apparently I''m either incorrect, or my compiler is incorrect (anyone know for sure? Couldn''t find the answer in my ARM). The fix follows, and it makes me feel a little dirty, but it works without leaks. I''ll leave the code to explain itself. The derived classes are unchanged from above:
  
// fixed filtermaker.h

// filtermaker.h

class FilterMaker
{
private:
  typedef map<int, FilterMaker*> FilterMap; // maps data type to

                                            // filter*

  static FilterMap *s_pHandlers; // note that it''s a ptr..

protected:
  // ctor called by derived classes to register type

  FilterMaker (int type);
  virtual void doFilter (Data &pkt) = 0; // actual filter code

                                         // in derived class

public:
  // function that client will call to filter data

  static void filterData (int type, Data &pkt);
};

// filtermaker.cpp

// it is very important that this static member be uninitialized

FilterMaker::FilterMap FilterMaker::s_pHandlers;
FilterMaker::FilterMaker (int type)
{
  static bool initialized = false;
  static FilterMap physicalMap;
  if (!initialized)
  {
    s_pHandlers = &physicalMap
    initialized = true;
  }
  s_pHandlers->insert (FilterMap::value_type (type, this));
}
void FilterMaker::filterData (int type, Data &pkt)
{
  (s_pHandlers)[type]->doFilter (pkt);
}
  
So, I have two questions: 1) Can anybody point out chapter-and-verse where the C++ spec covers this? Does it just fall into the category that "class statics are just like regular statics in that their ctors are called arbitrarily"? 2) Any major problems with this design? I am aware that it is non-reentrant (by design)
Advertisement
I''m not rich enough to own the current C++ standard, so I''ll have to quote from the draft, but:

3.6.2.1 [basic.start.init]

Objects of namespace scope with static storage duration defined in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit.

The definition, of course, is the actual initialisation statement.

The draft doesn''t appear to cover this, but I would assume that the ctors for seperate modules are executed in the order in which the modules are linked.

If you''re using VC++, chances are good that ExFilter.obj is ahead of FilterMaker.obj when linking (because VC++ does things alphabetically). If your actual Filter is called XyzFilter, then it may be that the ctors are executed in reverse order. Which ever way ''round it might be, it''d be worth checking.

Personally, I would feel more dirty about not explicitly indicating the order of initialisation than using your fix.

The best solution, IMO, is to do this:

  // filtermaker.hclass FilterMaker {  private:    typedef map<int, FilterMaker*> FilterMap;    static FilterMap *s_pHandlers;  protected:    FilterMaker (int type);    virtual void doFilter (Data &pkt) = 0;  public:    static void filterData (int type, Data &pkt);};// filtermaker.cpp// const.expr ctors are done before dynamic ctors...FilterMaker::FilterMap FilterMaker::s_pHandlers = NULL;FilterMaker::FilterMaker (int type){  if (!s_pHandlers) s_pHandlers = new FilterMap;  s_pHandlers->insert(FilterMap::value_type(type, this));}void FilterMaker::filterData (int type, Data &pkt){  (*s_pHandlers)[type]->doFilter(pkt);}  



All your bases belong to us (I know. It''s irony.)
CoV
I see a bit of a design flaw. The filtermaker acts as a collection of filters and doesn''t actually filter anything. The EXFilter doesn''t contain/care about the collection of filters and does the actual filtering. The actual filter doesn''t seem to be a filtermaker, and probably shouldn''t subclass from it.

Instead have an abstract base class called Filter, and inherit all filters from that. This way you can access the singleton filterMaker the same way everything else does and register through that, ensuring it has been initialized and Filtering is still done through the filtermaker.
Meyers answers that question in Effective C++ pp221-223.

The solution is to use the Singleton pattern (p127, Design Patters, GoF)

Have a private static pointer to the object, initialized to NULL.
Access it through a static member function which initializes it if it hasn''t been already.

  class A{  A();  A* instance;public:  A& GetInstance()   {     if (!instance) instance = new A;    return *instance;  }}  

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Thanks for the replies. Some comments:

Mayrel:
I never thought of switching the link order, but that would absolutely not be a good solution, though it would probably compile & run. This is a team (about 7 people) project, so there''s no way that would fly, though that does explain why the order is deterministic.

With your proposed solution, I have a problem with this line:
FilterMaker::FilterMap FilterMaker::s_pHandlers = NULL;
What if ExFilter::s_instance is constructed before FilterMaker::s_pHandlers? Won''t s_pHandlers be uninitialized and probably != NULL? When constructing, I would get an access violation when I try to dereference the map which is pointed to by an uninitialized pointer.

Big B:
Your criticism of my naming is well-deserved. The problem is that I''ve "translated" this from my project. The actual function going on behind the names is a "ResourceMaker", which makes resources from different kinds of events. As events are added, I add derivations of "ResourceMaker". I excluded all this because there was an extra parameter passed around (the list of resources) that wasn''t pertinent to the problem. We do call the methods "filter", because we filter from one kind of data into Resources, but I think I was misleading when I typed it this way. So maybe if you look at it that way the names make more sense.

The reason I inherited the other "ResourceMakers" from "ResourceMaker" was that I thought this would ensure the order of static member initialization. Now that it appears not to, I think you have a point--split it off into an abstract class and do it there, make ResourceMaker a singleton.
quote:Original post by Stoffel
Mayrel:
I never thought of switching the link order, but that would absolutely not be a good solution, though it would probably compile & run.

Even without a team, that isn''t a good solution: you can''t be sure it''ll work on the next version of the compiler.
quote:
With your proposed solution, I have a problem with this line:
FilterMaker::FilterMap FilterMaker::s_pHandlers = NULL;
What if ExFilter::s_instance is constructed before FilterMaker::s_pHandlers? Won''t s_pHandlers be uninitialized and probably != NULL? When constructing, I would get an access violation when I try to dereference the map which is pointed to by an uninitialized pointer.

This isn''t an issue. The initialiser for s_pHandlers is constant, which means that it is handled before the initialiser for s_instance, which is dynamic: constant static initialisers are handled before dynamic static initialisers.

All your bases belong to us (I know. It''s irony.)
CoV
quote:
This isn''t an issue. The initialiser for s_pHandlers is constant, which means that it is handled before the initialiser for s_instance, which is dynamic: constant static initialisers are handled before dynamic static initialisers.

Well that''s really handy to know! I really need to get a full copy of the spec; all I have is the ARM, and it''s pretty old now (my copy''s from ''90).
quote:Original post by Stoffel
Well that''s really handy to know! I really need to get a full copy of the spec; all I have is the ARM, and it''s pretty old now (my copy''s from ''90).


I use the ''96 draft, which you can find at http://www.csci.csusb.edu/dick/c++std/cd2/index.html.

All your bases belong to us (I know. It''s irony.)
CoV

This topic is closed to new replies.

Advertisement