One issue of global variables is that initialization order is undefined across translation units, and we have some practices to avoid the global variables. But I still want to understand the initialization order of global variables across translation units, just for education purposes.
Suppose we have code like this:
action_type.h
struct ActionType {
static const ActionType addShard; // struct static variables
}
action_type.cpp
ActionType ActionType::addShard(addShardValue);
action_set.h
ActionSet(ActionType s);
my.cpp:
// global variables
ActionSet s(ActionType::addShard);
My questions are:
==== To make the topic 2 discussed easier, here is my test code ====
// cat action.h
#ifndef ACTION_H
#define ACTION_H
#include <iostream>
#include <bitset>
namespace m {
class ActionSet {
public:
ActionSet();
ActionSet(std::initializer_list<int> ids);
void dump() const;
private:
std::bitset<4> _actions;
};
}
#endif /* ACTION_H */
// action.cpp
#include "action.h"
#include <iostream>
namespace m {
ActionSet::ActionSet(): _actions(0) {
std::cout << "from default" << std::endl;
}
ActionSet::ActionSet(std::initializer_list<int> ids) {
std::cout << "from init list.." << std::endl;
for(auto id : ids) {
_actions.set(id, true);
}
}
void ActionSet::dump() const {
for(int i=0; i<4; i++) {
std::cout << _actions[i] << ",";
}
std::cout << std::endl;
}
}
// const.h
#ifndef CONST_H
#define CONST_H
namespace m {
struct X {
static int x;
static int y;
};
}
#endif /* CONST_H */
// const.cpp
#include "const.h"
namespace m {
int X::x = 0;
int X::y = 2;
};
// f.h
#ifndef F_H
#define F_H
#include "action.h"
#include <iostream>
namespace m {
void f1();
void f2();
}
#endif /* F_H */
// f.cpp
#include "f.h"
#include "const.h"
namespace m {
const ActionSet s{X::x, X::y};
void f1() {
s.dump();
}
void f2() {
const ActionSet s2{X::x, X::y};
s2.dump();
}
};
// action.h
#ifndef ACTION_H
#define ACTION_H
#include <iostream>
#include <bitset>
namespace m {
class ActionSet {
public:
ActionSet();
ActionSet(std::initializer_list<int> ids);
void dump() const;
private:
std::bitset<4> _actions;
};
}
#endif /* ACTION_H */
// action.cpp
#include "action.h"
#include <iostream>
namespace m {
ActionSet::ActionSet(): _actions(0) {
std::cout << "from default" << std::endl;
}
ActionSet::ActionSet(std::initializer_list<int> ids) {
std::cout << "from init list.." << std::endl;
for(auto id : ids) {
_actions.set(id, true);
}
}
void ActionSet::dump() const {
for(int i=0; i<4; i++) {
std::cout << _actions[i] << ",";
}
std::cout << std::endl;
}
}
// main.cpp
#include "f.h"
int main(int argc, char *argv[])
{
m::f1();
m::f2();
return 0;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(project_name)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_EXTENSIONS off)
set(CMAKE_EXPORT_COMPILE_COMMANDS on)
set( CMAKE_VERBOSE_MAKEFILE on )
add_executable(main const.cpp main.cpp f.cpp action.cpp)
add_executable(main2 main.cpp f.cpp action.cpp const.cpp)
You have a lot of code to go through, unfortunately, I'm unable to find out what ActionType
actually is.
As you indicated, using Global Variables is indeed a bad idea. Luckily, they added constexpr
to the language.
With constexpr
you can create constants which are 'defined' at compile time without having runtime impact. So regardless of the order in which you Ctors get executed, it will produce the right result.
On the bad side of things, std::initializer_list
ain't constexpr (not even in C++20), std::bitset
is.
#include <bitset>
struct ActionType {
static constexpr std::bitset<4> addShard{0b0101};
};
With the code above, you made a constexpr
variable that can safely be used to initialize a global variable. Similarly, you could create your next type as constexpr
available:
class ActionSet {
public:
constexpr ActionSet();
ActionSet(std::initializer_list<int> ids);
constexpr ActionSet(std::bitset<4> actions) : _actions{actions} {}
void dump() const;
private:
std::bitset<4> _actions{0};
};
constexpr ActionSet s(ActionType::addShard);
In short, as long as you are able to 'define' everything at compile time (including all the required code in headers), you can create constants based on other constants. Calling constant methods on them can be done at run time later on.
From C++20 on, you should be able to write:
[[constinit]] ActionSet s(ActionType::addShard);
This should allow you from using non-const methods within your program. It's unclear to me if this still allows you to use 's' within the constructor of the next constexpr variable.