全局变量初始化顺序
Global variable initialization order
全局变量的一个问题是跨翻译单元的初始化顺序未定义,我们有一些做法可以避免使用全局变量。但是我还是想了解跨翻译单元的全局变量的初始化顺序,仅供参考。
假设我们有这样的代码:
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);
我的问题是:
- 我总能从全局 "s" 变量中获取准确的值吗? s 取决于在不同翻译单元中定义的 ActionType::addShard。
- 如果不能保证,我怎么会compile/link/run得到错误的结果呢?我听说顺序取决于 link 阶段。
====为了让话题2讨论的更简单,这里是我的测试代码====
// 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)
你有很多代码要完成,不幸的是,我无法找出 ActionType
到底是什么。
如您所说,使用全局变量确实是个坏主意。幸运的是,他们在语言中添加了 constexpr
。
使用 constexpr
,您可以在编译时创建 'defined' 的常量,而不会产生 运行 时间影响。因此,无论 Ctor 的执行顺序如何,它都会产生正确的结果。
不利的一面是,std::initializer_list
不是 constexpr(甚至在 C++20 中也不是),std::bitset
是。
#include <bitset>
struct ActionType {
static constexpr std::bitset<4> addShard{0b0101};
};
通过上面的代码,您创建了一个 constexpr
变量,可以安全地用于初始化全局变量。同样,您可以将下一个类型创建为 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);
简而言之,只要你能够在编译时'define'一切(包括headers中所有需要的代码),你就可以在其他常量的基础上创建常量。稍后可以在 运行 时调用它们的常量方法。
从 C++20 开始,您应该能够编写:
[[constinit]] ActionSet s(ActionType::addShard);
这应该允许您在程序中使用 non-const 方法。我不清楚这是否仍然允许您在下一个 constexpr 变量的构造函数中使用 's'。
全局变量的一个问题是跨翻译单元的初始化顺序未定义,我们有一些做法可以避免使用全局变量。但是我还是想了解跨翻译单元的全局变量的初始化顺序,仅供参考。
假设我们有这样的代码:
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);
我的问题是:
- 我总能从全局 "s" 变量中获取准确的值吗? s 取决于在不同翻译单元中定义的 ActionType::addShard。
- 如果不能保证,我怎么会compile/link/run得到错误的结果呢?我听说顺序取决于 link 阶段。
====为了让话题2讨论的更简单,这里是我的测试代码====
// 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)
你有很多代码要完成,不幸的是,我无法找出 ActionType
到底是什么。
如您所说,使用全局变量确实是个坏主意。幸运的是,他们在语言中添加了 constexpr
。
使用 constexpr
,您可以在编译时创建 'defined' 的常量,而不会产生 运行 时间影响。因此,无论 Ctor 的执行顺序如何,它都会产生正确的结果。
不利的一面是,std::initializer_list
不是 constexpr(甚至在 C++20 中也不是),std::bitset
是。
#include <bitset>
struct ActionType {
static constexpr std::bitset<4> addShard{0b0101};
};
通过上面的代码,您创建了一个 constexpr
变量,可以安全地用于初始化全局变量。同样,您可以将下一个类型创建为 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);
简而言之,只要你能够在编译时'define'一切(包括headers中所有需要的代码),你就可以在其他常量的基础上创建常量。稍后可以在 运行 时调用它们的常量方法。
从 C++20 开始,您应该能够编写:
[[constinit]] ActionSet s(ActionType::addShard);
这应该允许您在程序中使用 non-const 方法。我不清楚这是否仍然允许您在下一个 constexpr 变量的构造函数中使用 's'。