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)