c++ - OneOfAType 容器——将每个给定类型的容器存储在一个容器中——我在这里是否偏离了基地?

标签 c++

我遇到了一个有趣的问题,它出现在我的一种基于传递的编译器中。每个 channel 对其他 channel 一无所知,一个公共(public)对象按照命令链模式沿链向下传递。

传递的对象是对文件的引用。

现在,在其中一个阶段中,人们可能希望关联一大块数据,例如该文件的 SHA512 哈希,这需要合理的计算时间。但是,由于该数据 block 仅在特定情况下使用,我不希望所有文件引用都需要为该 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
}

但出于某种原因,我觉得这种设计不对,因为它用非类型化指针做了一大堆事情。我在这里严重偏离基地吗?是否有预先存在的库(boost 或其他)可以使这个更清晰/更容易理解?

最佳答案

正如 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< T >() 在程序的任何位置发出。

现在我们正在使用它来实现我们称为 value 的顶级类型. valuevalue_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) {}
};

这是值(value):

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 来说是相当乏味的。例如,对值可以存储的类型几乎没有要求:它们需要是可复制构造的并且具有公共(public)析构函数。如果您使用的所有类型都有一个 operator<<(ostream&,T) 并且您想要一种打印字典的方法怎么办?只需将 to_stream 方法添加到 box 并为值重载 operator<< ,您就可以编写:

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

这是一个包含上述内容的 pastebin,应该使用 g++/boost 开箱即用地编译:http://pastebin.com/v0nJwVLW

编辑:添加优化以避免从堆中分配 box_impl: http://pastebin.com/pqA5JXhA

关于c++ - OneOfAType 容器——将每个给定类型的容器存储在一个容器中——我在这里是否偏离了基地?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3221501/

相关文章:

c++ - boost shared_ptr 析构函数中未处理的异常异常

c++ - 当使用特定的枚举标识符时,打印出带有建议更正的静态 "deprecate"警告消息?

c++ - 声明类和命名空间的问题

c++ - 为什么 std::stof 在传递它无法转换的参数时不抛出?

C++:线程间共享的静态函数成员,可以全部阻塞吗?

c++ - C++中的单元测试

c++ - 为什么 C++ 需要运算符同义词?

c++ - 有什么方法可以在代码中格式化大数字以使其更易于阅读?

c++ - C++模板打包参数

c++ - 将整数更改为二进制数字字符串