Sign in to follow this  
King of Men

Static initialisation problem

Recommended Posts

I have a class ResourceHolder which is basically a wrapper for an array. It has a static lookup method to find the name of a numbered resource, which I store in a std::vector<std::string> called 'names'. This vector is initialised in method called 'initialise', like so:
void ResourceHolder::initialise () {
  static bool initialised = false;
  if (initialised) return;
  initialised = true;

  numResources = 9;
  names.resize(numResources);
}


which I call from the ResourceHolder constructor:
ResourceHolder::ResourceHolder () {
  initialise();
  theArray = new int[numResources];
  clear();
}



Actually, I just realised that there's no particular reason for the numResources initialisation anymore, it could now just as well be a static const, but it's that way for historical reasons. Anyway. I have some of these ResourceHolders scattered about my code as static class variables. Now, when I try to call on the 'names' vector, the size of the vector is zero. But I can see the initialisation code being run and exiting with names.size() == 9, just as it should. When I do this:
int main (int argc, char** argv) {
  ResourceHolder b;
  ResourceHolder::setName(0, "Labour");
  std::cout << "Try 0" << ResourceHolder::getName(0) << std::endl;
  return 0;
}



I get this for output:
Initialising
Exiting with 9
Asked for name 0 of 9 0x804d2a4
Try 0 Labour
which is fine. But when I link to those pieces of my code that contain static ResourceHolders, I get instead
Initialising
Exiting with 9
test: ResourceHolder.cc:34: static int ResourceHolder::define(unsigned int, 
std::basic_string<char, std::char_traits<char>, 
std::allocator<char> >): Assertion `names.size() > idx' failed.
Observe that names has somehow been reset, so its size is now zero again. I conclude that this must be some issue with the static initialisation, but I don't understand how this can lead to a variable that's been very nicely set, thanks kindly, to be cleared again in this fashion! How can I avoid this resetting of the names vector? It's late, I hope this is clear.

Share this post


Link to post
Share on other sites
is 'names' a global std::vector? i'm guessing that it is, in which case your static/global ResourceHolder objects' constructors are firing before the constructor of your global/static 'names' vector. Your ResourceHolder constructors resize it, but later, when the constructor for 'names' fires, it clears out the vector (causing a memory leak, as the default constructor for std::vector is likely just setting its members to 0/NULL).

Share this post


Link to post
Share on other sites
If your situation is as I described, then a quick and simple fix is the following.

Replace your

static std::vector names;

with

std::vector& getNames( )
{
static std::vector names;
return names;
}


Now, the constructor for the names vector won't fire until the first time you call getNames( ), but it will be forced to fire before you actually access it and call resize on it.

A perhaps better alternative in the long run is to try to avoid globals, though I'm not claiming that they're not appropriate sometimes.

Share this post


Link to post
Share on other sites
names is not global; it's a static member of ResourceHolder, like so:


class ResourceHolder : public Holder {
public:
ResourceHolder ();
ResourceHolder (ResourceHolder const* const c);

static unsigned int numResources;

static std::string getName (unsigned int idx);
static int define (unsigned int idx, std::string name);

bool equals (ResourceHolder const* const o) const;

protected:
virtual void initialise ();
virtual unsigned int holderSize () {return numResources;}

private:

// Static lookups for attributes of individual resources
static std::vector<std::string> names;
};





It's defined in the corresponding .cc, here:


#include "ResourceHolder.hh"
#include <assert.h>

unsigned int ResourceHolder::numResources = 0;
std::vector<std::string> ResourceHolder::names;

#include <iostream>

ResourceHolder::ResourceHolder () {
initialise();
theArray = new int[numResources];
clear();
}

ResourceHolder::ResourceHolder (ResourceHolder const* const c) {
theArray = new int[numResources];
for (unsigned int i = 0; i < numResources; ++i) {
theArray[i] = c->theArray[i];
}
}

bool ResourceHolder::equals (ResourceHolder const* const o) const {
for (unsigned int i = 0; i < numResources; ++i) {
if (theArray[i] != o->theArray[i]) return false;
}
return true;
}

int ResourceHolder::define (unsigned int idx, std::string name) {
assert(idx < numResources);
assert(names.size() > idx);
names[idx] = name;
return 1;
}

std::string ResourceHolder::getName (unsigned int idx) {
std::cout << "Asked for name " << idx << " of " << names.size() << " " << &names << std::endl;
return names[idx];
}

void ResourceHolder::initialise () {
static bool initialised = false;
if (initialised) return;
initialised = true;

numResources = 9;
std::cout << "Initialising" << std::endl;

names.resize(numResources);
std::cout << "Exiting with " << names.size() << std::endl;
}




And this is the calling code:


#include "ResourceHolder.hh"
//#include "Province.hh"
#include <iostream>

int main (int argc, char** argv) {
std::cout << "Here" << std::endl;
ResourceHolder b;
ResourceHolder::define(0, "Labour");
std::cout << "Try 3" << ResourceHolder::getName(0) << std::endl;
return 0;
}





That's all the calling code you need to get the crash - just uncomment the "#include Province.hh" and link in the corresponding object, which has a static ResourceHolder declared in it. If you don't do that, it doesn't crash. Obviously my actual program is a bit more complex, but I wanted to pare it down a bit for this debugging purpose. :)

It seems to me that emeyex has put his finger on the problem even though names is not global; I don't see anything in his description of the event chain that wouldn't apply to a static class member. Right? So I'll try his solution. Thanks!

Share this post


Link to post
Share on other sites
Just saw the reply... FWIW, static member is for all intents and purposes the same as global (same linkage, same rules (or lack thereof) with regards to order of initialization). I tend to use the two terms interchangeably.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this