簡體   English   中英

C ++模板std :: tuple to void * and back

[英]C++ templates std::tuple to void* and back

我正在嘗試使用C ++ 11和可變參數模板創建資源管理器。 問題是如何將std :: tuple存儲到集合中並將其恢復? 在這個例子中我試圖將它存儲為void *(嘗試不使用boost :: any)。 每次我回到std :: tuple時,我都會得到那個cast'ed元組與params創建的元組相同(currentArgs == storedArgs)。 我認為下面的代碼解釋了一切。

http://ideone.com/h3yzvy

#include <memory>
#include <typeindex>
#include <iostream>
#include <string>
#include <vector>
#include <map>

typedef std::multimap<std::type_index, void*> Object;
typedef std::map<Object, std::shared_ptr<void>> ObjectCollection;

Object object;
ObjectCollection objectCollection;

template<typename T, typename... Args>
T* getResource(Args&& ... args)
{
    // Creating tuple from the arguments
    std::tuple<Args...> currentArgs(std::forward<Args>(args)...);
    // Getting object type info
    std::type_index type = { typeid(T) };

    // Getting all objects from the collection that are of the same type
    auto range = object.equal_range(type);

    for (auto it = range.first; it != range.second; ++it)
    {
        // it->second is a void* Since we are iterating through
        // the the collection of the same type I'm trying to cast
        // back. Object construct parameters should be the same 
        // (in this example: const std::string &fileName)
        auto storedArgs = *static_cast<std::tuple<Args...>*>(it->second);

        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        // Problem is here. currentArgs and storedArgs are always equal :/
        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        // Return the object from the collection if current arguments and
        // arguments from the collection are the same
        if (currentArgs == storedArgs)
        {
            std::cout << "Found... returning..." << std::endl;
            // found... return...
            return static_cast<T*>(objectCollection[object].get());
        }
    }

    // Object with the same arguments were not found
    // Adding to collection and return
    std::cout << "Adding to collection..." << std::endl;
    object.emplace(type, &currentArgs);
    objectCollection[object] = std::make_shared<T>(std::forward<Args>(args)...);
    return static_cast<T*>(objectCollection[object].get());
}

class Resource
{
public:
    virtual ~Resource() = default;

    template<typename T, typename... Args>
    static T* get(Args&& ... args)
    {
        return getResource<T>(std::forward<Args>(args)...);
    }
};

class Image
{
public:
    Image(const std::string &fileName)
    {
        std::cout << "Loading image " << fileName.c_str() << std::endl;
    }

    ~Image(){};
};

int main()
{
    auto image1 = Resource::get<Image>("aaa.jpg");
    auto image2 = Resource::get<Image>("bbb.jpg");
    auto image3 = Resource::get<Image>("aaa.jpg");
    getchar();
}

編輯

感謝大家的投入。 如果有人關心我的最終Resource.h看起來像這樣並且工作完美:

#pragma once

#include <memory>
#include <map>

template<class T, class...Args>
std::map<std::tuple<Args...>, std::shared_ptr<T>>& getCache()
{
    static std::map<std::tuple<Args...>, std::shared_ptr<T>> cache; // only run once
    return cache;
}

template<typename T, typename... Args>
std::shared_ptr<T> getResource(Args&& ... args)
{
    // std::decay_t should be used
    auto& cache = getCache<T, std::decay_t<Args>...>();

    // Creating tuple from the arguments
    auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);

    // Search for object in the cache
    auto it = cache.find(arguments);

    if (it != cache.end())
    {
        // Found. Return.
        return it->second;
    }

    // Not found. Add to cache.
    auto object = std::make_shared<T>(std::forward<Args>(args)...);
    cache.emplace(std::make_pair(std::move(arguments), object));
    return object;
}

class Resource
{
public:
    virtual ~Resource() = default;

    template<typename T, typename... Args>
    static std::shared_ptr<T> get(Args&& ... args)
    {
        return getResource<T>(std::forward<Args>(args)...);
    }
};

為什么不為每種類型和args使用一個函數本地映射? 由於您已經按照這兩個條件過濾了數據,因此可以簡化代碼:

#include <iostream>
#include <math.h>

using namespace std; 

#include <memory>
#include <typeindex>
#include <iostream>
#include <string>
#include <vector>
#include <map>

template<typename T, typename... Args>
std::shared_ptr<T> getResource(Args&& ... args)
{
    static std::map<std::tuple<Args...>, std::shared_ptr<T>> objectCollection;

    // Creating tuple from the arguments
    std::tuple<Args...> currentArgs(std::forward<Args>(args)...);

    //Search for object in map
    auto objectIter = objectCollection.find(currentArgs);

    if(objectIter != objectCollection.end())
    {
        std::cout << "Found... returning..." << std::endl;
        return objectIter->second;
    }

    std::shared_ptr<T> newObject(new T(args...));
    std::cout << "Adding to collection..." << std::endl;
    objectCollection.insert(std::pair<std::tuple<Args...>, std::shared_ptr<T>>(currentArgs, newObject));
    return newObject;
}

class Resource
{
public:
    virtual ~Resource() = default;

    template<typename T, typename... Args>
    static std::shared_ptr<T> get(Args&& ... args)
    {
        return getResource<T>(std::forward<Args>(args)...);
    }
};

class Image
{
public:
    Image(const std::string &fileName)
    {
        std::cout << "Loading image " << fileName.c_str() << std::endl;
    }

    ~Image() {};
};

int main()
{
    auto image1 = Resource::get<Image>("aaa.jpg");
    auto image2 = Resource::get<Image>("bbb.jpg");
    auto image3 = Resource::get<Image>("aaa.jpg");
    getchar();
}

編輯:我也改變了代碼,一直使用shared_ptr。

您正在存儲指向函數的局部變量的指針:

// declaration of local variable "currentArgs"
std::tuple<Args...> currentArgs(std::forward<Args>(args)...);

// ...

// storing the pointer of "currentArgs" in "object"
object.emplace(type, &currentArgs);

該局部變量( currentArgs )存在於堆棧中,並且從函數返回后,指向它的指針變為無效。 巧合(因為你從同一個地方調用函數),下次調用函數時,變量的地址完全相同,這意味着解除引用你的(無效)指針解析為currentArgs的當前值。

為了避免這個問題,使用newmake_shared創建一個永久對象,並在地圖object中將原始指針或smartpointer放入其中。

在這一行:

 object.emplace(type, &currentArgs);

字符數組(或傳入的任何類型)將在堆棧上。 那不是你可以擁有的存儲空間。 它沒有通過任何指針分配使用和存儲,更不用說void *,這意味着該指針的內容來自堆棧。

在這些行的每次通話中:

   auto image1 = Resource::get<Image>("aaa.jpg");
   auto image2 = Resource::get<Image>("bbb.jpg");

和其他任何人一樣,堆棧恰好在每次調用之前處於相同的狀態。 這意味着當調用“bbb.jpg”時,'emplace'調用指向SAME內存,但現在變為“bbb.jpg”而不是“aaa.jpg”。 由於堆棧在該程序的未來版本中的其他地方使用,堆棧將由於正在運行的程序而改變,這意味着所存儲的對象的內容將改變,看似隨機。

你必須做的是重新考慮存儲。

您可以分配要存儲的對象的新副本,但這會帶來另一個問題。 您已在ObjectCollection中存儲了shared_ptr。 它不會知道如何刪除它。 實際上,shared_ptr“擁有”的指針可以是任何東西,包括需要銷毀的C ++類或結構(如在delete p中,其中p是對所述對象的void *)。 它不知道如何做到這一點,因為shared_ptr只“知道”這是一個空*。 它只會執行void *的刪除,並且永遠不會調用該對象的析構函數。 為了使其有效,您必須確保只有POD類型(不需要調用析構函數)才有效。 簡單地說,對於您正在使用的上下文,您不能使用shared_ptr作為確保處理內存的方法,因為它不僅僅是釋放內存,而是您必須處理的破壞。

您可以創建對象的副本,這些副本不是由void存儲的,但這意味着map和multimap不能存儲任何對象。

這是boost :: any的目的,但是如果你不能使用它,你必須重新考慮如何處理地圖中對象的銷毀,或者你必須將存儲限制為不存在的類型需要析構函數。

最終確定解決方案的解決方案有太多潛在的解決方案(我將為您構建產品,並為您做出設計選擇)。

我可以告訴你解決方案所需的功能。

你必須取消shared_ptr。 你不能依賴“自動”釋放,這是你使用shared_ptr的目的。 在銷毀時,您別無選擇,只需遍歷所有包含的條目,將它們轉換為真實類型,然后“手動”刪除它們。 你是如何做到這一點有無數的可能性。

您的代碼有一些基本錯誤。

首先,您使用推導為轉發引用的類型,就好像它們是值類型一樣。 Args&&...導出轉發引用,這意味着Args可以是值或引用類型。 std::tuple<Args>可能是一個引用元組。 這不是你想要的。

其次,你試圖避免boost::any ,然后你重新實現它錯了。 boost::any是一個void* 以及有關如何復制/銷毀/將其強制轉換回原始類型的信息。 簡單地存儲void*行不通的; 並且存儲指向自動存儲變量(堆棧變量)的指針將是完全垃圾。

每個類型條目的不同地圖很誘人,但一個體面的程序需要能夠清除它們。

這是一個.clear()類型的擦除視圖對象。 它刪除了對任意類型的對象調用.clear()的操作:

struct clear_later {
  void*p = nullptr;
  void(*f)(void*) = nullptr;
  template<class O,
    std::enable_if_t<!std::is_same<std::decay_t<O>,clear_later>{}>* = nullptr
  >
  clear_later( O&& o ):
    p(std::addressof(o)),
    f([](void* p){
      auto*po = static_cast<std::decay_t<O>*>(p);
      po->clear();
    })
  {};
  clear_later(clear_later const&)=default;
  clear_later()=default;
  void operator()()const{
    if (f) f(p);
  }
  explicit operator bool()const{ return f; }
  template<class Self>
  friend auto make_tie(Self&&self){
    return std::tie( std::forward<Self>(self).p, std::forward<Self>(self).f );
  }
  friend bool operator<( clear_later lhs, clear_later rhs )const{
    return make_tie(lhs) < make_tie(rhs);
  }
};

現在我們可以構建一組要清除的緩存,其中緩存是不同的類型:

std::vector<clear_later> caches_to_clear;
void clear_caches() {
  for (auto&& clear:caches_to_clear)
    clear();
}

現在我們需要一種方法來自動注冊創建的緩存。 我們也希望能夠“透明地”查找,因此我們使用std::less<void>進行搜索:

template<class T, class...Args>
std::map< std::tuple<Args...>, T, std::less<> >& make_and_register_cache() {
  static std::map< std::tuple<Args...>, T, std::less<>> retval; // actual storage
  caches_to_clear.emplace_back(retval);
  return retval;
}
template<class T, class...Args>
std::map< std::tuple<Args...>, T, std::less<>>& get_cache() {
  static auto& cache = make_and_register_cache(); // only run once
  return cache;
}

最后:

template<typename T, typename... Args>
std::shared_ptr<T> getResource(Args&& ... args)
{
  // notice the use of decay.  This is important:
  auto& cache = get_cache<T, std::decay_t<Args>...>();

  // Creating tuple from the arguments (via forwarding)
  auto currentArgs = std::forward_as_tuple(std::forward<Args>(args)...);

  //Search for object in map
  auto objectIter = cache.find(currentArgs);

  if(objectIter != cache.end()) {
    std::cout << "Found... returning..." << std::endl;
    return objectIter->second;
  }

  // note lack of forward, and use of make_shared.  Never forward twice!
  auto newObject = std::make_shared<T>(args...);
  std::cout << "Adding to collection..." << std::endl;
  // get rid of extra copy of args you made here by calling emplace
  // move of forwarding tuple activates forwarding:
  cache.emplace(std::move(currentArgs), std::move(newObject));
  return newObject;
}

我們現在可以將內容添加到緩存中。 我們不在緩存中存儲引用(與您的版本不同)。 我們可以通過調用clear_caches來清除每個緩存。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM