【问题标题】:Alternative id generators for types类型的替代 id 生成器
【发布时间】:2018-12-22 06:59:08
【问题描述】:

a project of mine 中,我有一个ID generator 用于看起来与此类似的类型:

class Family {
    static std::size_t identifier;

    template<typename...>
    static std::size_t family() {
        static const std::size_t value = identifier++;
        return value;
    }

public:
    template<typename... Type>
    inline static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

std::size_t Family::identifier{};

用法:

const auto id = Family::type<FooBar>();

对于我的目的来说它工作得很好,但它有一些限制。最烦人的一个(问题的目的)是,如果所有链接到共享库的可执行文件都尝试创建标识符,它就会失败。结果通常是因为每个共享库都维护自己独立的Family::identifier,所以第n 个标识符被跨边界分配给不同的类型。

一些共享库人员指出,如果有一个更可靠的解决方案,我们将不胜感激,但没有提出一个不会破坏性能的解决方案(几乎所有这些人都引入了容器、查找功能和内存分配)。

是否有任何替代方法可以解决上述限制而不损失当前设计的性能?

我搜索了 SO 并找到了一些有趣的答案。其中许多已经有好几年了。只要现有类的接口保持不变,我想探索最新版本的标准的解决方案。
This one 是最有趣的一个。它使用静态成员的地址来实现相同的目的,但它不符合顺序生成标识符的想法

注意:很遗憾,不能使用 RTTI。

注意 : ids 必须按顺序生成,从 0 开始,如上述解决方案。

【问题讨论】:

  • 那是很多 C++ 版本标签。最好专注于您实际使用的版本,因为所有以前的版本都是隐含的。
  • @tadman 实际上,一个至少适用于其中一个标签的解决方案对我来说很好。这就是为什么我把它们都放进去。如果您发现它们不是必需的,请随时编辑列表。我不确定还有哪些其他标签适合此类问题。感谢您的帮助。
  • @Justin 好点。不,RTTI 不是一个选项。我正在编辑问题以添加此注释。谢谢。
  • 我会使用静态类成员的地址作为唯一的类型标识符。只要 ODR 统治一天,一个空的class Identifier{};,我所有的类中都有一个静态类成员Identifier id;,并且类成员的地址作为我的标识符。为了迂腐正确,我总是使用 std::less&lt;Identifier *&gt;; 来比较我的伪 ID,因为在 C++11 之后,std::less 保证实现不相关指针的严格弱排序。
  • 比这更小心地使用标签。如果你能做 C++17,那就太好了,C++14 和 C++11 标签是多余的。当人们寻找要回答的问题时,他们不太可能寻找 C++11 特定的问题,他们会寻找 C++。使用其他标签来尝试对您的问题进行分类。例如,Boost 是一种选择吗?

标签: c++ shared-libraries c++17


【解决方案1】:

您的问题出现是因为您的头文件中有这一行:

std::size_t Family::identifier{};

因此它最终出现在每个翻译单元中。相反,您需要将此存储移动到仅编译一次的 .cpp 源文件中,可能会移动到它自己的共享库中。那么程序中将只有一个identifier 实例,它会按您的意愿工作。

您还可以将 identifier 从类 static 变量移动到头文件中的全局 extern 变量(如上所述,在单个 .cpp 文件中定义它)。

如果你有 C++17 或更高版本,也可以试试:

inline std::size_t Family::identifier{};

虽然该语言不保证(甚至不提及)跨共享库边界使用此新功能时会发生什么,但它确实适用于我的机器。

【讨论】:

  • 这可能不起作用,因为相同的类型,当编译到不同的共享库中时,将有自己的typefamily 定义,导致不同共享库中id 的值不同文件。
  • @1201ProgramAlarm:我试过了,效果很好。
  • 将代码放在单独的 .DLL/.SO 文件中。由于 DLL 和 EXE 具有不同的 typefamily 副本,因此您将在 EXE 和 DLL 中获得不同的 Family::type&lt;FooBar&gt;(); 值。
  • 那么这在仅标头库中是不可能的。 C++ 不能以这种方式工作。实际上,在头文件中这样做违反了 ODR,这就是你得到重复标识符的原因:未定义的行为。
  • @phön:谢谢,我确实尝试过,它在我的机器上运行,但我认为没有任何理由相信它可以在每个平台上运行。我在回答中添加了关于 inline 的注释。
【解决方案2】:

如果您不要求 id 是连续整数,则可以使用模板的静态成员的地址作为 id。这种方法的好处是它不需要任何运行时初始化(使用static initialization):

// in a header
class Family {
    template<class...> struct Id { static char const id; };

    template<typename... T>
    static std::size_t family() {
        return reinterpret_cast<std::size_t>(&Id<T...>::id);
    }

public:
    template<typename... Type>
    static std::size_t type() {
        return family<std::decay_t<Type>...>();
    }
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
int main() {
    auto int_id = Family::type<int>();
    auto int_int_id = Family::type<int, int>();
}

您也可以将 id 设为编译时常量并将其用作模板参数:

// in a header
struct Family {
    template<class...> struct Id { static char const id; };
};

// in a header
template<class... T>
char const Family::Id<T...>::id = {};

// elsewhere    
template<char const*>
struct X {};

int main() {
    X<&Family::Id<int>::id> x;
}

【讨论】:

    【解决方案3】:

    如果您不关心顺序 ID,请使用函数的地址作为标识符。

    template<typename... T>
    uintptr_t getID() {
        return reinterpret_cast<uintptr_t>(&getID<T...>);
    }
    

    然后

    auto intID = getID<int>();
    auto floatID = getID<float>();
    ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-02
      • 1970-01-01
      • 2018-07-03
      • 2016-05-02
      • 1970-01-01
      • 2015-08-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多