views:

671

answers:

3

Hello.

I have some questions about initializing a static collection. Here is an example I coded that seems to work:

#include <stack>
#include <iostream>

using namespace std;

class A
{
    private:
     static stack<int> numbers;

     static stack<int> initializeNumbers();

    public:
     A();
};

A::A() { cout << numbers.top() << endl; }

stack<int> A::initializeNumbers()
{
    stack<int> numbers;

    numbers.push(42);

    return numbers;
}

stack<int> A::numbers = initializeNumbers();

int main()
{
    A a;
}

Now, is this the best way to do what I'm trying to accomplish? For some reason, when I try this exact same scheme in my real code, calling top() prints gibberish. Could there be any reason for this?

If my example is fine, perhaps I will resort to posting my real code.


Here is the real code:

Light.h

#ifndef LIGHT_H_
#define LIGHT_H_

#include <stack>

#include "Vector4.h"

class Light
{
    private:
     static stack<GLenum> availableLights;

     static stack<GLenum> initializeAvailableLights();

    public:
     GLenum lightID;
     Vector4 ambient, diffuse, specular, position, spotDirection;
     GLfloat constantAttenuation, linearAttenuation, quadraticAttenuation, spotExponent, spotCutoff;

     Light( const Vector4& ambient = Vector4(0.0, 0.0, 0.0, 1.0),
       const Vector4& diffuse = Vector4(1.0, 1.0, 1.0, 1.0),
       const Vector4& specular = Vector4(1.0, 1.0, 1.0, 1.0),
       const Vector4& position = Vector4(0.0, 0.0, 1.0, 0.0),
       const Vector4& spotDirection = Vector4(0.0, 0.0, -1.0, 0.0),
       GLfloat constantAttenuation = 1.0,
       GLfloat linearAttenuation = 0.0,
       GLfloat quadraticAttenuation = 0.0,
       GLfloat spotExponent = 0.0,
       GLfloat spotCutoff = 180.0);

     ~Light();
};

#endif /*LIGHT_H_*/

Light.cpp

#include <stdexcept>    // runtime_error
#include <iostream>

using namespace std;

#include "Light.h"

Light::Light(   const Vector4& ambient,
       const Vector4& diffuse,
       const Vector4& specular,
       const Vector4& position,
       const Vector4& spotDirection,
       GLfloat constantAttenuation,
       GLfloat linearAttenuation,
       GLfloat quadraticAttenuation,
       GLfloat spotExponent,
       GLfloat spotCutoff) :

       ambient(ambient),
       diffuse(diffuse),
       specular(specular),
       position(position),
       spotDirection(spotDirection),
       constantAttenuation(constantAttenuation),
       linearAttenuation(linearAttenuation),
       quadraticAttenuation(quadraticAttenuation),
       spotExponent(spotExponent),
       spotCutoff(spotCutoff)
{
    // This prints gibberish.
    cout << availableLights.size() << endl;

    // The error is indeed thrown.
    if(availableLights.empty())
     throw runtime_error("The are no more available light identifiers.");
    else
    {
     lightID = availableLights.top();

     availableLights.pop();
    }
}

Light::~Light() { availableLights.push(this -> lightID); }

stack<GLenum> Light::initializeAvailableLights()
{
    stack<GLenum> availableLights;

    availableLights.push(GL_LIGHT7);
    availableLights.push(GL_LIGHT6);
    availableLights.push(GL_LIGHT5);
    availableLights.push(GL_LIGHT4);
    availableLights.push(GL_LIGHT3);
    availableLights.push(GL_LIGHT2);
    availableLights.push(GL_LIGHT1);
    availableLights.push(GL_LIGHT0);

    return availableLights;
}

stack<GLenum> Light::availableLights = initializeAvailableLights();


And since I can't get the code with the stack to work, I've opted for this at the moment:

Light.h

#ifndef LIGHT_H_
#define LIGHT_H_

#include <stack>

#include "Vector4.h"

class Light
{
    private:
     static const unsigned int LIGHTS = 9;
     static bool availableLights[];
     static GLenum lights[];

     static GLenum getAvailableLight();

    public:
     GLenum lightID;
     Vector4 ambient, diffuse, specular, position, spotDirection;
     GLfloat constantAttenuation, linearAttenuation, quadraticAttenuation, spotExponent, spotCutoff;

     Light( const Vector4& ambient = Vector4(0.0, 0.0, 0.0, 1.0),
       const Vector4& diffuse = Vector4(1.0, 1.0, 1.0, 1.0),
       const Vector4& specular = Vector4(1.0, 1.0, 1.0, 1.0),
       const Vector4& position = Vector4(0.0, 0.0, 1.0, 0.0),
       const Vector4& spotDirection = Vector4(0.0, 0.0, -1.0, 0.0),
       GLfloat constantAttenuation = 1.0,
       GLfloat linearAttenuation = 0.0,
       GLfloat quadraticAttenuation = 0.0,
       GLfloat spotExponent = 0.0,
       GLfloat spotCutoff = 180.0);

     ~Light();
};

#endif /*LIGHT_H_*/

Light.cpp

#include <stdexcept>    // runtime_error

#include "Light.h"

Light::Light(   const Vector4& ambient,
       const Vector4& diffuse,
       const Vector4& specular,
       const Vector4& position,
       const Vector4& spotDirection,
       GLfloat constantAttenuation,
       GLfloat linearAttenuation,
       GLfloat quadraticAttenuation,
       GLfloat spotExponent,
       GLfloat spotCutoff) :

       ambient(ambient),
       diffuse(diffuse),
       specular(specular),
       position(position),
       spotDirection(spotDirection),
       constantAttenuation(constantAttenuation),
       linearAttenuation(linearAttenuation),
       quadraticAttenuation(quadraticAttenuation),
       spotExponent(spotExponent),
       spotCutoff(spotCutoff)
{
    lightID = getAvailableLight();
}

Light::~Light()
{
    for(unsigned int i = 0; i < LIGHTS; i++)
     if(lights[i] == lightID)
      availableLights[i] = true;
}

bool Light::availableLights[] = {true, true, true, true, true, true, true, true};
GLenum Light::lights[] = {GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, GL_LIGHT3, GL_LIGHT4, GL_LIGHT5, GL_LIGHT6, GL_LIGHT7};

GLenum Light::getAvailableLight()
{
    for(unsigned int i = 0; i < LIGHTS; i++)
     if(availableLights[i])
     {
      availableLights[i] = false;

      return lights[i];
     }

    throw runtime_error("The are no more available light identifiers.");
}


Can anyone spot an error in the code with the stack, or perhaps improve upon my hastily coded workaround?

+1  A: 

I don't think that code will even compile (missing A:: from initializeNumbers() for a start).

I suggest you post your real code.

However, why do you not just initialize the stack on the first constructor call (with thread protection if you're running in a multithreaded environment of course).

That seems to be a much cleaner way of doing it, something like (untested):

#include <stack>
#include <iostream>
using namespace std;
class A {
    private:
        static boolean isInited = false;
        static stack<int> numbers;
    public:
        A();
};

A::A() {
    if (!isInited) {
        numbers.push(42);
        isInited = true;
    }
    cout << numbers.top() << endl; }

int main() {
    A a;
}
paxdiablo
That shouldn't be a problem though because it's being returned by value. The copy of the stack (while inneficient if much larger) should be fine.
JaredPar
But I'm passing the local stack by value and assigning it immediately to my static stack, so should the copy constructor get called for my static stack?
Because then I have to make that ugly isInited check. :-) I posted the real code. Save me!
Ugly maybe, though debatable and definitely functional. My advice is to go with this. Are you trying to be clever or are you trying to deliver software? If the former, by all means ignore my advice.Re "Save me" - I've already saved you. If you decide to jump into the pit again, that's your problem, not mine :-)
paxdiablo
Is it just me or does the original example work?
GMan
A: 

There can definitely be a reason for this. There are really two issues with this approach in general. The first problem is that static initializers run before the main method starts in C++. This means that initializeNumbers will run before anything else in the program. In this limited sample that's not much of a problem.

The second issue though is that C++ does not provide any guarantee to ordering. Meaning if you have more than one statically initialized variable, the compiler is free to initialize them in whatever order it pleases. So if you have a dependency on one static variable in anothers initializer you will run into bugs (and creating a subtle dependency is very easy to do).

You're probably much better off here doing some form of delayed initialization for complex static values.

JaredPar
The problem is that I need to pop values off of this stack for instances of my class. I can't delay initialization!
@Scott, you can't pop until your object is created so create the entries in the constructor - see my answer. I still fear per-main initialization of complex objects for the reasons given by @Jared - I'll always prefer to do them in the constructor.
paxdiablo
A: 

Wondering if something along these lines would help you. I still prefer Pax's suggestion, it's simpler, but this should avoid your "is init" worries.

class NumStack : public stack<int>
{
public:
   NumStack(){
      push(42);
   }
};

class A
{
    private:
        static NumStack numbers;

    public:
        A();
};

//in cpp file, do as usual for static members
NumStack A::Numbers;

If the inheritence makes you quesy (which it should, it aint pretty), just aggregate stack into NumStack. This will require some changes to the usage of numbers in the code though:

class NumStack
{
public:
   NumStack(){
      obj.push(42);
   }
   stack<int> obj;
};
Snazzer
STL containers are not designed for inheritance; this should be done with caution or (my preference) not at all.
Patrick Johnmeyer
Yea for sure, I edited to show aggregation as an alternative, but I figured inheritence would be ok in this case since usage is quite limited due to NumStack having no members.
Snazzer