Контейнер OneOfAType - хранит по одному в контейнере заданного типа - я не здесь?

У меня есть интересная проблема, которая возникла в каком-то моем компиляторе на основе проходов. Каждый проход ничего не знает о других проходах, и общий объект передается по цепочке, следуя шаблону цепочки команд.

Передаваемый объект является ссылкой на файл.

Теперь, на одном из этапов, можно пожелать связать большой кусок данных, такой как хеш этого файла SHA512, который требует разумного количества времени для вычисления. Однако, поскольку этот фрагмент данных используется только в этом конкретном случае, я не хочу, чтобы для всех ссылок на файлы требовалось резервировать пространство для этого SHA512. Тем не менее, я также не хочу, чтобы другим проходам приходилось пересчитывать хэш SHA512 снова и снова. Например, кто-то может принимать только файлы, которые соответствуют заданному списку SHA512, но они не хотят, чтобы это значение печаталось, когда ссылка на файл попадает в конец цепочки, или, возможно, они хотят и то, и другое... или... и т. Д.

Что мне нужно, это какой-то контейнер, который содержит только один из данного типа. Если контейнер не содержит этот тип, ему нужно создать экземпляр этого типа и каким-то образом сохранить его. В основном это словарь с типом, который используется для поиска.

Вот что я получил до сих пор. FileData::Get<t> метод:

class FileData;
// Cache entry interface
struct FileDataCacheEntry
{
    virtual void Initalize(FileData&)
    {
    }
    virtual ~FileDataCacheEntry()
    {
    }
};

// Cache itself
class FileData
{
    struct Entry
    {
        std::size_t identifier;
        FileDataCacheEntry * data;
        Entry(FileDataCacheEntry *dataToStore, std::size_t id)
            : data(dataToStore), identifier(id)
        {
        }
        std::size_t GetIdentifier() const
        {
            return identifier;
        }
        void DeleteData()
        {
            delete data;
        }
    };
    WindowsApi::ReferenceCounter refCount;
    std::wstring fileName_;
    std::vector<Entry> cache;
public:
    FileData(const std::wstring& fileName) : fileName_(fileName)
    {
    }
    ~FileData()
    {
        if (refCount.IsLastObject())
            for_each(cache.begin(), cache.end(), std::mem_fun_ref(&Entry::DeleteData));
    }
    const std::wstring& GetFileName() const
    {
        return fileName_;
    }

    //RELEVANT METHOD HERE
    template<typename T>
    T& Get()
    {
        std::vector<Entry>::iterator foundItem = 
            std::find_if(cache.begin(), cache.end(), boost::bind(
            std::equal_to<std::size_t>(), boost::bind(&Entry::GetIdentifier, _1), T::TypeId));
        if (foundItem == cache.end())
        {
            std::auto_ptr<T> newCacheEntry(new T);
            Entry toInsert(newCacheEntry.get(), T::TypeId);
            cache.push_back(toInsert);
            newCacheEntry.release();
            T& result = *static_cast<T*>(cache.back().data);
            result.Initalize(*this);
            return result;
        }
        else
        {
            return *static_cast<T*>(foundItem->data);
        }
    }
};

// Example item you'd put in cache
class FileBasicData : public FileDataCacheEntry
{
    DWORD    dwFileAttributes;
    FILETIME ftCreationTime;
    FILETIME ftLastAccessTime;
    FILETIME ftLastWriteTime;
    unsigned __int64 size;
public:
    enum
    {
        TypeId = 42
    }
    virtual void Initialize(FileData& input)
    {
        // Get file attributes and friends...
    }
    DWORD GetAttributes() const;
    bool IsArchive() const;
    bool IsCompressed() const;
    bool IsDevice() const;
    // More methods here
};

int main()
{
    // Example use
    FileData fd;
    FileBasicData& data = fd.Get<FileBasicData>();
    // etc
}

По какой-то причине этот дизайн кажется мне неправильным, а именно потому, что он делает целую кучу вещей с нетипизированными указателями. Я серьезно не в курсе? Существуют ли уже существующие библиотеки (повышающие или иные), которые могли бы сделать это понятнее / проще для понимания?

2 ответа

Решение

Как уже сказал ergosys, std::map - очевидное решение вашей проблемы. Но я вижу, что вы беспокоитесь о RTTI (и связанных с этим вздор). На самом деле, "любой" контейнер значений не нуждается в RTTI для работы. Достаточно предоставить отображение между типом и уникальным идентификатором. Вот простой класс, который обеспечивает это отображение:

#include <stdexcept>
#include <boost/shared_ptr.hpp>
class typeinfo
{
    private:
        typeinfo(const typeinfo&); 
        void operator = (const typeinfo&);
    protected:
        typeinfo(){}
    public:
        bool operator != (const typeinfo &o) const { return this != &o; }
        bool operator == (const typeinfo &o) const { return this == &o; }
        template<class T>
        static const typeinfo & get()
        {
            static struct _ti : public typeinfo {} _inst;
            return _inst;
        }
};

typeinfo::get<T>() возвращает ссылку на простой синглтон без состояния, который позволяет проводить сравнения.

Этот синглтон создается только для типов T, где typeinfo:: get() выдается в любом месте программы.

Теперь мы используем это для реализации верхнего типа, который мы называем value, value является держателем для value_box который на самом деле содержит данные:

class value_box
{
    public:
        // returns the typeinfo of the most derived object
        virtual const typeinfo& type() const =0;
        virtual ~value_box(){}
};

template<class T>
class value_box_impl : public value_box
{
    private:
        friend class value;
        T m_val; 
        value_box_impl(const T &t) : m_val(t) {}
        virtual const typeinfo& type() const
        {
            return typeinfo::get< T >();
        }
};
// specialization for void.
template<>
class value_box_impl<void> : public value_box
{
    private:
        friend class value_box;
        virtual const typeinfo& type() const
        {
            return typeinfo::get< void >();
        }
    // This is an optimization to avoid heap pressure for the 
    // allocation of stateless value_box_impl<void> instances:
    void* operator new(size_t) 
    {
        static value_box_impl<void> inst;
        return &inst;
    }
    void operator delete(void* d) 
    {
    }

};

Вот исключение bad_value_cast:

class bad_value_cast : public std::runtime_error
{
    public:
        bad_value_cast(const char *w="") : std::runtime_error(w) {}
};

И вот значение:

class value
{
    private:
        boost::shared_ptr<value_box> m_value_box;       
    public:
        // a default value contains 'void'
        value() : m_value_box( new value_box_impl<void>() ) {}          
            // embedd an object of type T.
        template<class T> 
        value(const T &t) : m_value_box( new value_box_impl<T>(t) ) {}
        // get the typeinfo of the embedded object
        const typeinfo & type() const {  return m_value_box->type(); }
        // convenience type to simplify overloading on return values
        template<class T> struct arg{};
        template<class T>
        T convert(arg<T>) const
        {
            if (type() != typeinfo::get<T>())
                throw bad_value_cast(); 
            // this is safe now
            value_box_impl<T> *impl=
                      static_cast<value_box_impl<T>*>(m_value_box.get());
            return impl->m_val;
        }
        void convert(arg<void>) const
        {
            if (type() != typeinfo::get<void>())
                throw bad_value_cast(); 
        }
};

Удобный синтаксис приведения:

template<class T>
T value_cast(const value &v) 
{
    return v.convert(value::arg<T>());
}

И это все. Вот как это выглядит:

#include <string>
#include <map>
#include <iostream>
int main()
{
    std::map<std::string,value> v;
    v["zero"]=0;
    v["pi"]=3.14159;
    v["password"]=std::string("swordfish");
    std::cout << value_cast<int>(v["zero"]) << std::endl;
    std::cout << value_cast<double>(v["pi"]) << std::endl;
    std::cout << value_cast<std::string>(v["password"]) << std::endl;   
}

Приятно иметь собственную реализацию any в том, что вы можете очень легко адаптировать его к функциям, которые вам действительно нужны, что довольно утомительно с boost:: any. Например, существует несколько требований к типам, которые может хранить значение: они должны быть копируемыми и иметь открытый деструктор. Что если все типы, которые вы используете, имеют оператор<< (ostream &, T) и вам нужен способ печати словарей? Просто добавьте метод to_stream в box и перегрузите оператор << для значения, и вы можете написать:

std::cout << v["zero"] << std::endl;
std::cout << v["pi"] << std::endl;
std::cout << v["password"] << std::endl;

Вот пастин с вышеупомянутым, должен скомпилироваться из коробки с g ++ / boost: http://pastebin.com/v0nJwVLW

РЕДАКТИРОВАТЬ: Добавлена ​​оптимизация, чтобы избежать выделения box_impl из кучи: http://pastebin.com/pqA5JXhA

Вы можете создать хеш или карту строки для boost::any. Строковый ключ может быть извлечен из любого::type().

Другие вопросы по тегам