4


2

OneOfATypeコンテナ-コンテナに特定のタイプを1つずつ格納します-私はここから離れていますか?

ある種のパスベースのコンパイラで発生する興味深い問題があります。 各パスは他のパスをまったく認識せず、コマンドパターンのチェーンに従って、共通オブジェクトがチェーンを通過していきます。

渡されるオブジェクトは、ファイルへの参照です。

ここで、いずれかの段階で、ファイルのSHA512ハッシュなどの大量のデータを関連付けたい場合がありますが、これには計算にかなりの時間が必要です。 ただし、そのデータチャンクはその特定の場合にのみ使用されるため、すべてのファイル参照がそのSHA512用のスペースを予約する必要はありません。 ただし、他のパスでSHA512ハッシュを何度も再計算する必要もありません。 たとえば、SHA512の特定のリストに一致するファイルのみを受け入れる人がいるかもしれませんが、ファイル参照がチェーンの最後に達したときにその値を出力したくない場合、あるいは両方が必要な場合、または…​ 。等。

必要なのは、指定されたタイプの1つのみを含む何らかのコンテナです。 コンテナにそのタイプが含まれていない場合、そのタイプのインスタンスを作成し、何らかの方法で格納する必要があります。 これは基本的に、物事を調べるために使用されるタイプの辞書です。

ここに私がこれまでに得たものがあります。関連するビットは `FileData

Get`メソッドです:

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

何らかの理由で、このデザインは私にとって間違っていると感じています。つまり、型付けされていないポインタを使用して多くのことを行っているためです。 私はここでひどく離れていますか? これをより明確/理解しやすくする既存のライブラリ(ブーストまたはその他)がありますか?

2 Answer


8


ergosysがすでに述べたように、std

mapは問題の明らかな解決策です。 しかし、RTTI(および関連する肥大化)に懸念があることがわかります。 実際のところ、「任意の」値コンテナーは、RTTIが機能する必要はありません。 タイプと一意の識別子の間のマッピングを提供するだけで十分です。 このマッピングを提供する簡単なクラスを次に示します。

#include
#include
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
        static const typeinfo & get()
        {
            static struct _ti : public typeinfo {} _inst;
            return _inst;
        }
};
`typeinfo

get()`は比較を可能にするシンプルでステートレスなシングルトンへの参照を返します。

_このシングルトンは、typeinfo

get <T>()がプログラムの任意の場所で発行されるタイプTに対してのみ作成されます。

現在、これを使用して、「value」と呼ばれるトップ型を実装しています。 「値」は、実際にデータを含む「値ボックス」のホルダーです。

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

template
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 : 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 instances:
    void* operator new(size_t)
    {
        static value_box_impl 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 m_value_box;
    public:
        // a default value contains 'void'
        value() : m_value_box( new value_box_impl() ) {}
            // embedd an object of type T.
        template
        value(const T &t) : m_value_box( new value_box_impl(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 struct arg{};
        template
        T convert(arg) const
        {
            if (type() != typeinfo::get())
                throw bad_value_cast();
            // this is safe now
            value_box_impl *impl=
                      static_cast*>(m_value_box.get());
            return impl->m_val;
        }
        void convert(arg) const
        {
            if (type() != typeinfo::get())
                throw bad_value_cast();
        }
};

便利なキャスト構文:

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

以上です。 これはどのように見えるかです:

#include
#include
#include
int main()
{
    std::map v;
    v["zero"]=0;
    v["pi"]=3.14159;
    v["password"]=std::string("swordfish");
    std::cout << value_cast(v["zero"]) << std::endl;
    std::cout << value_cast(v["pi"]) << std::endl;
    std::cout << value_cast(v["password"]) << std::endl;
}
`any`の実装を所有することの良いところは、実際に必要な機能に合わせて非常に簡単に調整できることです。これは、boost

anyでは非常に面倒です。 たとえば、値が格納できる型に関する要件はほとんどありません。コピー構築可能で、パブリックデストラクタが必要です。 使用するすべてのタイプにoperator <<(ostream&、T)があり、辞書を印刷する方法が必要な場合はどうなりますか? to_streamメソッドをボックスに追加し、valueのoperator <<をオーバーロードするだけで、次のように記述できます。

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 <void>の割り当てを回避するための最適化を追加:http://pastebin.com/pqA5JXhA


1


boost

anyの文字列のハッシュまたはマップを作成できます。 文字列キーはany :: type()から抽出できます。