2015-11-30 2 views
1

Рассмотрим следующий код:CRTP и уникальные постоянные идентификаторы

#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 (мы можем легко его увидеть, выполнив несколько раз пример). Другими словами, учитывая два класса A и B, если происходит, что при запуске дважды программного обеспечения их члены identifier вызывается в другом порядке, у них разные идентификаторы.

Моя проблема в том, что по некоторым причинам мне нужно хранить эти идентификаторы где-то и позволить им пережить одно исполнение, чтобы я мог рассуждать о первоначальных типах, когда приложение запускается еще раз и решит прочитать эти значения из хранилище.

Альтернативой было бы использовать hash_code от type_info, но он страдает от других проблем. Другим решением будет принудительное обращение к членам identifier во время загрузки приложения, но у этого также есть несколько недостатков.

Я хотел бы знать, есть ли все еще простое в использовании, хотя все еще элегантное решение, которое полностью прозрачно для разработчика, чтобы идентифицировать типы по нескольким исполнениям, так как один из них приведен для одного запуска приложения.

ответ

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' и штампы все ?CLASS_ID? с приращенным счетчиком (начиная с числа уже сгенерированных идентификаторов). Чтобы добавить некоторую безопасность, вам может понадобиться более качественный образец, чем простой? ...?, Но я думаю, у вас есть идея.

+0

Что вы подразумеваете под внешним скриптом? Что-то вроде хранения идентификаторов в файле, который должен быть загружен при запуске программного обеспечения? – skypjack

+0

Что-то похожее на способ использования идентификаторов rcs. Вы отмечаете свой класс с этим специальным атрибутом при первом создании, а скрипт изменяет источник, когда значение не задано. Это часть компиляции, а не среда выполнения. – SergeyA

+0

Звучит интересно, поскольку я не могу загрузить что-либо во время выполнения. Мне жаль, что я не знаю, как работает rcs, но можете ли вы интегрировать свой ответ с некоторыми ссылками, чтобы лучше объяснить этот подход? В любом случае, спасибо, действительно оценил предложение. :-) – skypjack

0

Даже если они немного отличаются, так как вопросы, 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; 
    } 
} 

Основная проблема заключается в том, что идентификаторы могут измениться, если элемент удаляется из списка типов.
В любом случае, тривиально определить пустой заполнитель для решения проблемы.

Смежные вопросы