I have a class called Controller
, inside of which, I have a class called Button
. A Controller
contains several Button
instances of different types (eg button_type_a
, button_type_b
).
#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 int
s, and I would like to be able to associate certain button type int
s 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 Button
s with the map.
#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()
.
#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;
.
int
to Button*
mapping that is unaffected by compile order? 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:
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.
The initialization order of static variables is not defined so it depends on your particular setup, including compiler, linker and linking order.
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).
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;
}
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();
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.
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;
}
As and 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:
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".
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.