首页 > 解决方案 > 在程序启动时自动执行代码而不违反 ODR

问题描述

我尝试编写一种简单的方法来在程序启动时自动执行代码(不使用不可移植的属性())。

我写了下面的代码,我问如果在 main() 之前编写的代码放在头文件中,它是否违反了 ODR。或者,内联函数和变量会阻止这种情况吗?如果违反 ODR,是否会出现两次调用该函数的情况?

#include <iostream>

//-----

template <void(*init_function)()>
class execute_at_start
{
    struct dummy final {};
    ~execute_at_start() = delete;
    inline static dummy execute_() { init_function(); return dummy{}; }
    inline static dummy auto_init_ = execute_();
};

//-----

inline void echo(){ std::cout << "echo" << std::endl; }

template class execute_at_start<&echo>;

int main()
{
    std::cout << "EXIT SUCCESS" << std::endl;
    return EXIT_SUCCESS;
}

先感谢您

标签: c++c++17one-definition-rule

解决方案


您的解决方案应该是 ODR 安全的。auto_init_C++ 标准要求类模板的每个不同实例化只有一个静态数据成员实例,execute_at_start<>并且它被初始化一次。

Itanium C++ ABI(大多数编译器都遵守它,除了 MSVC)需要全局变量的保护变量(auto_init_此处)以确保它们被初始化一次:

如果函数范围的静态变量或具有模糊链接的静态数据成员(即类模板的静态数据成员)被动态初始化,则有一个关联的保护变量用于保证构造只发生一次。

有关更多详细信息,请参阅Itanium C++ ABI §2.8 初始化保护变量


另一个不依赖保护变量的标准 ODR 安全解决方案是Schwarz Counterstd::cout自 C++ 出现以来,它就被用于初始化标准流(和朋友)。在这个解决方案counter中是一个具有外部链接的显式保护变量,它确保只有第一次调用execute_at_start构造函数调用init_function

// Header begin.
#include <iostream>

template<void(*init_function)()>
class execute_at_start {
    static inline int counter = 0;
public:
    execute_at_start() {
        if(!counter++)
            init_function();
    }
};

inline void echo() { std::cout << "echo" << std::endl; }

execute_at_start<echo> const execute_at_start_echo; // A copy in each translation unit.

// Header end.

int main() {}

推荐阅读