【问题标题】:CRTP and unique persistent identifiersCRTP 和唯一的持久标识符
【发布时间】:2015-11-30 21:13:31
【问题描述】:

考虑以下代码:

#include <iostream>
#include <cstdlib>
#include <ctime>

struct BaseClass {
    static int identifier() {
        static int identifier_counter = 0;
        return identifier_counter++;
    }
};

template <class D>
struct Class: public BaseClass {
    static int identifier() {
        static int class_identifier = BaseClass::identifier();
        return class_identifier;
    }
};

struct A: public Class<A> { };
struct B: public Class<B> { };

int main() {
    std::srand(std::time(0));
    int r = std::rand()%2;

    if(r) {
        std::cout << "A: " << A::identifier() << std::endl;
        std::cout << "B: " << B::identifier() << std::endl;
    } else {
        std::cout << "B: " << B::identifier() << std::endl;
        std::cout << "A: " << A::identifier() << std::endl;
    }
}

这是对问题的简化但仍然合理的表示。

任何派生类在运行时都会有一个特定的、不同的标识符,并且相同类型的两个实例将共享相同的标识符。肯定是解决此类问题的好方法。

不幸的是,这些标识符取决于调用identifier 成员的顺序(我们可以通过多次运行示例来轻松看到)。换句话说,给定两个类AB,如果碰巧运行两次软件,它们的identifier 成员以不同的顺序被调用,它们就有不同的标识符。

我的问题是,由于某些原因,我需要将这些标识符存储在某处,并让它们在单次执行中存活下来,这样一旦应用程序再次运行,我就可以对原始类型进行推理,并决定从存储。

另一种方法是使用type_info 中的hash_code,但它会遇到其他问题。另一种解决方案是在应用程序的引导期间强制调用identifier 成员,但这也有几个缺点。

我想知道到目前为止是否有一种易于实现但仍然优雅的解决方案,它对开发人员完全透明,可以识别多次执行的类型,因为上面的解决方案是针对应用程序的单次运行。

【问题讨论】:

    标签: c++ templates crtp


    【解决方案1】:

    C++ 无法解决每个类都有唯一持久标识符的问题。对不起。您将取决于调用初始化函数的顺序,或者,如果您从静态对象的初始化程序调用它们,则取决于静态初始化程序的顺序(这通常取决于链接行中目标文件的顺序)。

    当然,也不能保证哈希是唯一的。

    您必须为此使用外部脚本。特别是,可能会使用这样的东西:

    // when class is first created
    class Foo {
      static int class_id = ?CLASS_ID?;
    };
    
    // after class is process by the script 
    class Foo {
      static int class_id = 123; // Autogenerated by 'stamp_id.pl'
    };
    

    你可能有一个 perl 脚本作为编译的一部分运行(第一件事),它打开项目目录中的所有 .h 文件,读取所有文件,计算 Autogenerated by 'stamp_id.pl' 的所有实例,然后标记所有 @987654323 @ 带有递增的计数器(从已经生成的 id 的数量开始)。为了增加一些安全性,您可能想要一个比简单的更好的模式?...?,但我想,你明白了。

    【讨论】:

    • 外部脚本是什么意思?类似于将标识符存储在执行软件时要加载的文件中?
    • 例如,类似于使用 rcs id 的方式。您在首次创建时使用该特殊属性标记您的类,并且在未设置值时脚本会修改源。它是编译的一部分,而不是运行时环境。
    • 听起来很有趣,因为我无法在运行时加载任何内容。很抱歉我对 rcs 的工作原理缺乏了解,但您能否将您的回复与一些链接结合起来以更好地解释这种方法?无论如何,谢谢您,非常感谢您的建议。 :-)
    【解决方案2】:

    即使它们作为问题略有不同,here 我提出了一个可能也适合这个问题的解决方案。
    它不是基于 CRTP 习语,它的优点是非侵入式解决方案。

    它遵循一个最小的工作示例:

    #include<cstddef>
    #include<functional>
    #include<iostream>
    
    template<typename T>
    struct wrapper {
        using type = T;
        constexpr wrapper(std::size_t N): N{N} {}
        const std::size_t N;
    };
    
    template<typename... T>
    struct identifier: wrapper<T>... {
        template<std::size_t... I>
        constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {}
    
        template<typename U>
        constexpr std::size_t get() const { return wrapper<U>::N; }
    };
    
    template<typename... T>
    constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}};
    
    // ---
    
    struct A {};
    struct B {};
    
    constexpr auto id = ID<A, B>;
    
    int main() {
        switch(id.get<B>()) {
        case id.get<A>():
            std::cout << "A" << std::endl;
            break;
        case id.get<B>():
            std::cout << "B" << std::endl;
            break;
        }
    }
    

    主要问题是如果从类型列表中删除元素,则 ID 可能会发生变化。
    无论如何,定义一个空的占位符来解决这个问题是微不足道的。

    【讨论】:

      猜你喜欢
      • 2011-11-19
      • 1970-01-01
      • 2018-06-30
      • 2012-06-01
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 2011-08-17
      • 2012-07-18
      相关资源
      最近更新 更多