Question

I have a class called Controller, inside of which, I have a class called Button. A Controller contains several Button instances of different types (e.g. button_type_a, button_type_b).


controller.h

#ifndef __controller__
#define __controller__
class Controller
{
public:
    class Button
    {
    public:
        Button(int type = -1);

    private:
        int type;
    };

    Controller();

    Button A;
    Button B;
    Button X;
    Button Y;
};
#endif


The button types are ints, and I would like to be able to associate certain button type ints with pointers to Button instances of those particular types.

To keep track of this association, I use a std::map<int, Controller::Button*>, which I typedef to a buttonmap_t.

As I create new Button instances (in the Controller constructor), the Button constructor registers the types of those Buttons with the map.


controller.cpp

#include "controller.h"
#include <map>

typedef std::map<int, Controller::Button*> buttonmap_t;
buttonmap_t map;

Controller::Controller() :
A(0),
B(1),
X(2),
Y(3)
{ }

Controller::Button::Button(int type) :
type(type)
{
    map[type] = this;
}


Then I create a global Controller object, and I define main().


main.cpp

#include <iostream>
#include "controller.h"

Controller controller;

int main(int argc, const char * argv[])
{
    std::cout << "running..." << std::endl;
    return 0;
}


Depending on the order in which I compile the sources, the program either runs fine, or triggers a segmentation fault:

apogee:MapTest$ gcc controller.cpp main.cpp -o maptest -lstdc++
apogee:MapTest$ ./maptest 
running...

apogee:MapTest$ gcc main.cpp controller.cpp -o maptest -lstdc++
apogee:MapTest$ ./maptest 
Segmentation fault: 11


It seems like the latter case is trying to use the map before it has been properly initialized, and that's causing a seg fault. When I debug with Xcode, the debugger stops in "__tree" as the std::map is calling __insert_node_at(), which throws EXC_BAD_ACCESS(code=1, address=0x0). The call stack reveals that this was triggered by the first Button instance calling map[type] = this;.

So, here's my multi-part question:

  1. Why does the compile order cause this to happen?
  2. Is there a way to achieve this kind of int to Button* mapping that is unaffected by compile order?
  3. If so, what is it?

Ideally, I'd still like to have all of the Controller- and Button-related code in the separate controller.* files.


It seems like this is somewhat related to (but not quite the same as) the following questions:

  1. Segmentation fault in std::map::insert(...)
  2. [] operator in std::map is giving me segmentation fault
Was it helpful?

Solution 2

The initialization order of static variables is not defined so it depends on your particular setup, including compiler, linker and linking order.

The Initializer Function Trick

You can use the initializer function trick to ensure that something is initialized when needed. I know that this works for certain on Windows with Microsoft's C++ compiler (and did a little testing on g++ with Linux, see below).

Initializer Function

The first step is to move the map into a function as a static variable and always access the map through this function.

buttonmap_t& buttonMap() {
  static buttonmap_t map;
  return map;
}

Usage

The map is created when the buttonMap() function is first called. If you access the map through the function then you can be sure that it will be created.

Controller::Button::Button(int type) :
  type_(type) {
    buttonMap()[type] = this;
}

The crucial part is the initialization of the global variable: you replace it with a reference and initialize it from the function that holds the variable.

buttonmap_t& map = buttonMap();

Explanation

With this setup the initialization order doesn't matter because the first call to the function will perform the initialization and every call after it will use the initialized instance.

Note: this trick works for global variables because the initialization phase is done on a single thread. Even if you don't know the exact order of initialization, you can be sure that it will happen sequentially.

Testing

I tested this on my home computer with g++ on Linux and it seems to work:

$ g++ main.cpp controller.cpp -Wall
$ ./a.out
running...

the final program:

// controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H

class Controller {
 public:
  class Button {
   public:
    Button(int type = -1);

   private:
    int type_;
  };

  Controller();

  Button A;
  Button B;
  Button X;
  Button Y;
};

#endif

// controller.cpp
#include "controller.h"
#include <map>

typedef std::map<int, Controller::Button*> buttonmap_t;

buttonmap_t& ButtonMap() {
  static buttonmap_t map;
  return map;
}

buttonmap_t& map = ButtonMap();

Controller::Controller() :
  A(0),
  B(1),
  X(2),
  Y(3) {
}

Controller::Button::Button(int type) :
  type_(type) {
  ButtonMap()[type] = this;
}

// main.cpp
#include "controller.h"
#include <iostream>
Controller controller;

int main(int argc, const char * argv[]) {
  std::cout << "running..." << std::endl;
  return 0;
}

OTHER TIPS

When you have multiple global constructors, the order in which they are executed is undefined. If the controller object is instantiated before the map object is, the controller won't be able to access that map, which results in the segfault. If, however, the map object is instantiated first, then the controller can access it just fine.

In this case, it seems to be the order in which they appear on the command line. That's why putting your main.cpp first caused the segfault - the controller object was getting instantiated first.

I'd recommend moving the instantiation inside your main, because then you can control exactly how objects are instantiated and in what order.

As @Drew McGowen and @jrok suggested, I checked out the "static initialization order fiasco":
http://www.parashift.com/c++-faq/static-init-order.html
http://www.parashift.com/c++-faq/static-init-order-on-first-use.html

Using the "construct on first use" idiom, I made the following modifications:


controller.cpp

typedef std::map<int, Controller::Button*> buttonmap_t;
static buttonmap_t& map() // was buttonmap_t map;
{
    static buttonmap_t* ans = new buttonmap_t();
    return *ans;
};

//[...]

Controller::Button::Button(int type) :
type(type)
{
    map()[type] = this;
}


This works as intended for either compile order, and it doesn't require any changes to "controller.h" or "main.cpp".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top