模板化Id生成器定义良好的行为?

Tim*_*ann 5 c++ undefined-behavior template-meta-programming c++17

我想避免为需要的Id所有类分配一个Id,所以我写了一个小的模板化id生成器.

BaseIdGenerator::makeUniqueId只需返回一个新的Id,每次调用它:

class BaseIdGenerator {
protected:
    static inline Id makeUniqueId() {
        static Id nextId = 0;
        return nextId++;
    }
};
Run Code Online (Sandbox Code Playgroud)

为了分配Id给各个类,该类只是作为模板参数传递给IdGenerator:

template <typename T>
Id getId();

template <typename T>
class IdGenerator final : public BaseIdGenerator {
    static Id id;

    template <typename Type>
    friend Id getId();
};

template <typename T>
inline Id getId() {
    return IdGenerator<T>::id;
}

template <typename T>
Id IdGenerator<T>::id = IdGenerator<T>::makeUniqueId();
Run Code Online (Sandbox Code Playgroud)

这将makeUniqueId()每个类调用一次(即使在C++ 11以来的多线程应用程序中,由于线程安全的本地静态变量)

在行动中,这看起来像这样:

int main() {
    std::cout << getId<int>() << std::endl;  // prints 0
    std::cout << getId<bool>() << std::endl; // prints 1
    std::cout << getId<char>() << std::endl; // prints 2
}
Run Code Online (Sandbox Code Playgroud)

这按预期工作.

  • 这是C++中明确定义的行为吗?
  • 许多用途会getId()打破这个功能吗?(多个源文件等)
  • 这种行为是否标准化,因此在每台机器上输出都是相同的,并且在网络应用程序中它将按预期工作?

Cur*_*ous 3

这将为每个类调用一次 makeUniqueId() (即使是在自 C++11 以来的多线程应用程序中,由于线程安全的局部静态变量)

局部变量的初始化static是线程安全的。 不修改它们,因此只需在函数中使用本地静态变量即可确保它在多线程程序中构造一次且仅一次。您手动执行的任何其他操作都容易出现竞争条件,并且需要在您端进行同步。例如,如果您makeUniqueId()从多个线程同时调用,上面的内容很容易出现竞争条件。

这篇维基百科文章很好地解释了静态局部变量构造的工作原理以及如何防止多线程访问。但再次注意,只有静态局部变量的构造才受到语言和编译器的保护。

这是 C++ 中明确定义的行为吗?

正如您现在所拥有的,假设您的所有代码都可以编译,是的,这是定义明确的行为,它不会违反 ODR,就好像这就是您的想法一样。但是,如果您在实现文件中专门化getId()和/或IdGenerator类并将其链接到另一个看不到该专门化的文件,那么您就违反了 ODR,因为现在系统中同一事物有两个定义(一个是专业的和非专业的)。在这种情况下,编译器甚至不需要发出任何诊断警告。因此,尽管这按原样工作,但要小心并了解 ODR。有关更多信息,请参阅http://en.cppreference.com/w/cpp/language/definition

getId() 的多次使用会破坏此功能吗?(多个源文件等)

您可以getId()根据需要多次使用。但是,静态变量初始化顺序未指定,因此getId()可能不会始终返回相同的值。但是,如果您没有种族和 ODR 违规行为(如上所述),您将拥有不同的价值观

这种行为是否标准化,因此在每台机器上的输出都是相同的,并且在网络应用程序中它将按预期工作?

静态变量初始化顺序在翻译单元中未指定,所以我想说它在每台机器上可能不一样。我并没有真正了解这对于网络应用程序来说会如何改变。初始化值的代码在程序启动之前运行。因此,所有 id 都将在任何网络 I/O 之前设置(假设在运行之前调用的静态函数中没有网络 I/O main(),在这种情况下,其中一个值getId()甚至可能不会被初始化)