Dangerous situation after using extern instead of #define

Started by
6 comments, last by SiCrane 16 years, 1 month ago
I had following defines in a header:
#define Vector3_0 Vector3(0.0,0.0,0.0)
#define Vector3_x Vector3(1.0,0.0,0.0)
#define Vector3_y Vector3(0.0,1.0,0.0)
#define Vector3_z Vector3(0.0,0.0,1.0)
Avoiding macro's is a good thing (tm) plus it's always calling the constructor if you use it, so I changed it into:
In the header:

extern const Vector3 Vector3_0;
extern const Vector3 Vector3_x;
extern const Vector3 Vector3_y;
extern const Vector3 Vector3_z;

In a .cpp file:

const Vector3 Vector3_0 = Vector3(0.0,0.0,0.0);
const Vector3 Vector3_x = Vector3(1.0,0.0,0.0);
const Vector3 Vector3_y = Vector3(0.0,1.0,0.0);
const Vector3 Vector3_z = Vector3(0.0,0.0,1.0);
I had done that together with multiple changes so it wasn't immediatly clear that this could be the cause of crashes. Anyway after that my game suddenly had a crash, and a velocity that became "NaN" instead of a velocity. It took a long time to track down, but eventually I found out that some other objects were also being created on the heap before main() was called. And the order of that isn't defined, and something that was using Vector3_x & co was already being called before Vector3_x & co themselves had been created. This unlike the #defines! What should I do in this case? Go back to the #defines? Or avoid creating objects on the heap before main() is called? Does there exist another alternative to the #defines that is less dangerous? Maybe make them static? [Edited by - Lode on March 6, 2008 7:17:42 AM]
Advertisement
I prefer
class Vector3{  public:    static Vector3 Zero()    {      // Alternatively return an on-demand cached static member       // if you care about "construction cost."      return Vector3(0,0,0);    }};
Objects with constructors are subject to initialization order problems. You can reduce those issues by using globals with aggregate initializers. Ex:
struct Vector3Helper {  float x;  float y;  float z;  operator Vector3() { return Vector3(x, y, z); }};extern const Vector3Helper Vector3_0;extern const Vector3Helper Vector3_x;extern const Vector3Helper Vector3_y;extern const Vector3Helper Vector3_z;const Vector3Helper Vector3_0 = { 0, 0, 0 };const Vector3Helper Vector3_x = { 1, 0, 0 };const Vector3Helper Vector3_y = { 0, 1, 0 };const Vector3Helper Vector3_z = { 0, 0, 1 };
SiCrane, that's very interesting because I was also dealing with problems such as "should I make the Vector3 POD or give it a convenient extra constructor?". Sadly the one excludes the other.

Is it guaranteed that objects without constructor will all be initialized first (so the Vector3Helpers), and only later the other objects?
From 3.6.2 paragraph 1 of the C++ Standard:
Quote:
Objects of POD types ... with static storage duration initialized with constant expressions ... shall be initialized before any dynamic initialization takes place.

Quote:Original post by Lode
I had following defines in a header:

#define Vector3_0 Vector3(0.0,0.0,0.0)#define Vector3_x Vector3(1.0,0.0,0.0)#define Vector3_y Vector3(0.0,1.0,0.0)#define Vector3_z Vector3(0.0,0.0,1.0)


Avoiding macro's is a good thing (tm) plus it's always calling the constructor if you use it, so I changed it into:

In the header:extern const Vector3 Vector3_0;extern const Vector3 Vector3_x;extern const Vector3 Vector3_y;extern const Vector3 Vector3_z;In a .cpp file:const Vector3 Vector3_0 = Vector3(0.0,0.0,0.0);const Vector3 Vector3_x = Vector3(1.0,0.0,0.0);const Vector3 Vector3_y = Vector3(0.0,1.0,0.0);const Vector3 Vector3_z = Vector3(0.0,0.0,1.0);


I had done that together with multiple changes so it wasn't immediatly clear that this could be the cause of crashes.

Anyway after that my game suddenly had a crash, and a velocity that became "NaN" instead of a velocity. It took a long time to track down, but eventually I found out that some other objects were also being created on the heap before main() was called.

And the order of that isn't defined, and something that was using Vector3_x & co was already being called before Vector3_x & co themselves had been created.

This unlike the #defines!

What should I do in this case? Go back to the #defines? Or avoid creating objects on the heap before main() is called? Does there exist another alternative to the #defines that is less dangerous? Maybe make them static?


I think the real solution is ultimately to try to avoid, as much as possible, having code that executes before main(), except for objects that are meant to be constant. That's the real source of your issue, but if it's rampant through your code, you might want to just consider gradually working towards this goal.
Here's a possible way to avoid SOI problems, but it's obviously a hack around existing bad design (and kinda smells of singletonitis, too):

// beforeFoo x;Bar y;Baz z;int main() {  doStuffWith(x);};// afterstruct Global {  Foo x;  Bar y;  Baz z;  // use constructor/destructor logic to ensure everything is done in the right order.};Global* global; // a weak pointer; don't delete!int main() {  // Only ever instantiate it here:  std::auto_ptr<Global> global = new Global();  ::global = global; // allow other functions access.  // everything else  doStuffWith(global->x);  // RAII takes care of the rest.}


I haven't actually tested this approach, but I've had the idea for a while.
Now you've coupled every global to every other global. So if the type definition of Foo changes, then every file that uses y and z will need to be recompiled even if they previously had no dependency on x.

This topic is closed to new replies.

Advertisement