OneOfAType容器 - 在容器中存储每个给定类型的一个 - 我可以在这里吗? [英] OneOfAType container -- storing one each of a given type in a container -- am I off base here?
问题描述
我有一个有趣的问题,在我的一个基于pass的编译器。每个pass都不知道其他pass,并且一个普通的对象沿着链条传递,按照命令模式链。
I've got an interesting problem that's cropped up in a sort of pass based compiler of mine. Each pass knows nothing of other passes, and a common object is passed down the chain as it goes, following the chain of command pattern.
正在传递的对象是对文件的引用。
The object that is being passed along is a reference to a file.
现在,在其中一个阶段,可能希望关联一大块数据,例如该文件的SHA512散列,合理的计算时间。但是,由于该数据块仅用于该特定情况,我不希望所有文件引用需要为该SHA512保留空间。然而,我也不想要其他传递必须重新计算SHA512哈希一遍又一遍。例如,某人可能只接受与给定的SHA512列表匹配的文件,但是当文件引用到达链接末尾时,他们不希望打印该值,或者他们想要两个,或者... .etc。
Now, during one of the stages, one might wish to associate a large chunk of data, such as that file's SHA512 hash, which requires a reasonable amount of time to compute. However, since that chunk of data is only used in that specific case, I don't want all file references to need to reserve space for that SHA512. However, I also don't want other passes to have to recalculate the SHA512 hash over and over again. For example, someone might only accept files which match a given list of SHA512s, but they don't want that value printed when the file reference gets to the end of the chain, or perhaps they want both, or... .etc.
我需要的是一种只包含给定类型之一的容器。如果容器不包含该类型,则它需要创建该类型的实例并以某种方式存储它。它基本上是一个字典,类型是用来查找东西的东西。
What I need is some sort of container which contain only one of a given type. If the container does not contain that type, it needs to create an instance of that type and store it somehow. It's basically a dictionary with the type being the thing used to look things up.
这里是我到目前为止,相关位是 FileData :: Get< t>
方法:
Here's what I've gotten so far, the relevant bit being the FileData::Get<t>
method:
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
}
对我来说,也就是因为它做了一大堆事情与无类型的指针。我在这里严重偏离基地吗?
For some reason though, this design feels wrong to me, namely because it's doing a whole bunch of things with untyped pointers. Am I severely off base here? Are there preexisting libraries (boost or otherwise) which would make this clearer/easier to understand?
推荐答案
由于ergosys已经说过,std :: map是你的问题的明显解决方案。但我可以看到你对RTTI(和相关的膨胀)的关注。事实上,任何值容器不需要RTTI工作。提供类型和唯一标识符之间的映射就足够了。这里是一个简单的类,提供这个映射:
As ergosys said already, std::map is the obvious solution to your problem. But I can see you concerns with RTTI (and the associated bloat). As a matter of fact, an "any" value container does not need RTTI to work. It is sufficient to provide a mapping between a type and an unique identifier. Here is a simple class that provides this mapping:
#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>()
返回一个简单的无状态单例的引用,允许比较。
typeinfo::get<T>()
returns a reference to a simple, stateless singleton which allows comparisions.
这个单例只是为类型T创建的,其中typeinfo :: get<
This singleton is created only for types T where typeinfo::get< T >() is issued anywhere in the program.
现在我们使用它来实现一个顶层类型,我们调用 value
。 value
是实际包含数据的 value_box
的持有者。
Now we are using this to implement a top type we call value
. value
is a holder for a value_box
which actually contains the data:
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异常:
Here's the bad_value_cast exception:
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>());
}
就是这样。以下是它的样子:
And that's it. Here is how it looks like:
#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;
}
关于拥有
是,你可以很容易地定制到你实际需要的功能,这是相当繁琐的boost :: any。例如,对值可以存储的类型有很少的要求:它们需要是可复制构造的并且具有公共析构函数。如果你使用的所有类型都有一个操作符<<(ostream&,T),你想要一种方法来打印你的字典呢?只需添加一个to_stream方法到box和重载operator<<对于值,您可以写:
The nice thing about having you own implementation of any
is, that you can very easily tailor it to the features you actually need, which is quite tedious with boost::any. For example, there are few requirements on the types that value can store: they need to be copy-constructible and have a public destructor. What if all types you use have an operator<<(ostream&,T) and you want a way to print your dictionaries? Just add a to_stream method to box and overload operator<< for value and you can write:
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
Here's a pastebin with the above, should compile out of the box with g++/boost: http://pastebin.com/v0nJwVLW
编辑:添加了优化以避免分配box_impl < void>:
http://pastebin.com/pqA5JXhA
EDIT: Added an optimization to avoid the allocation of box_impl< void > from the heap: http://pastebin.com/pqA5JXhA
这篇关于OneOfAType容器 - 在容器中存储每个给定类型的一个 - 我可以在这里吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!