简体   繁体   English

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

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

I'm trying to make a resource manager using C++11 and variadic templates. 我正在尝试使用C ++ 11和可变参数模板创建资源管理器。 The question is how to store std::tuple to collection and get it back? 问题是如何将std :: tuple存储到集合中并将其恢复? I've tried to store it to void* in this example (trying not to use boost::any here). 在这个例子中我试图将它存储为void *(尝试不使用boost :: any)。 Every time I'm casting back to std::tuple I'm getting that cast'ed tuple is the same as tuple created from the params (currentArgs == storedArgs). 每次我回到std :: tuple时,我都会得到那个cast'ed元组与params创建的元组相同(currentArgs == storedArgs)。 The code below I think explains everything. 我认为下面的代码解释了一切。

http://ideone.com/h3yzvy 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();
}

EDIT 编辑

Thanks everybody for the input. 感谢大家的投入。 In case anyone cares my final Resource.h looks like this and works perfect: 如果有人关心我的最终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)...);
    }
};

Why don't you use one function-local map per type and args? 为什么不为每种类型和args使用一个函数本地映射? Since you are already filtering your data by these 2 conditions it could simplify your code: 由于您已经按照这两个条件过滤了数据,因此可以简化代码:

#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();
}

EDIT: I also changed the code to use shared_ptr all the way through. 编辑:我也改变了代码,一直使用shared_ptr。

You are storing a pointer to a local variable of your function: 您正在存储指向函数的局部变量的指针:

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

// ...

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

That local variable ( currentArgs ) lives on the stack and the pointer to it becomes invalid after returning from the function. 该局部变量( currentArgs )存在于堆栈中,并且从函数返回后,指向它的指针变为无效。 By coincidence (because you call the function from the same place), the next time you call the function the address of the variable is exactly the same, which means dereferencing your (invalid) pointer resolves to the current value of currentArgs . 巧合(因为你从同一个地方调用函数),下次调用函数时,变量的地址完全相同,这意味着解除引用你的(无效)指针解析为currentArgs的当前值。

In order to avoid the problem, create a permanent object using new or make_shared and put the raw pointer or smartpointer to it in the map object . 为了避免这个问题,使用newmake_shared创建一个永久对象,并在地图object中将原始指针或smartpointer放入其中。

In this line: 在这一行:

 object.emplace(type, &currentArgs);

The character array (or whatever type is passed in) will be on the stack. 字符数组(或传入的任何类型)将在堆栈上。 That is not storage you can own. 那不是你可以拥有的存储空间。 It was not allocated for use and storage via any pointer, let alone void *, which means the content of that pointer is from the stack. 它没有通过任何指针分配使用和存储,更不用说void *,这意味着该指针的内容来自堆栈。

At each call in these lines: 在这些行的每次通话中:

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

And any others that follow, the stack happens to be in the same state before each call. 和其他任何人一样,堆栈恰好在每次调用之前处于相同的状态。 This means when "bbb.jpg" is called, the 'emplace' call is pointing to the SAME memory, but that has now become "bbb.jpg" instead of "aaa.jpg". 这意味着当调用“bbb.jpg”时,'emplace'调用指向SAME内存,但现在变为“bbb.jpg”而不是“aaa.jpg”。 As the stack is used elsewhere in future versions of this program, the stack will be changing as a result of a running program, meaning the content of the stored object(s) will change, seemingly randomly. 由于堆栈在该程序的未来版本中的其他地方使用,堆栈将由于正在运行的程序而改变,这意味着所存储的对象的内容将改变,看似随机。

What you must do is reconsider storage. 你必须做的是重新考虑存储。

You could allocate a new copy of the object you want to store, but that presents another problem. 您可以分配要存储的对象的新副本,但这会带来另一个问题。 You've stored a shared_ptr in ObjectCollection. 您已在ObjectCollection中存储了shared_ptr。 It will not know how to delete that. 它不会知道如何删除它。 In reality, the pointer "owned" by shared_ptr could be anything, including a C++ class or struct, which REQUIRES destruction (as in delete p, where p is the void * cast to said object). 实际上,shared_ptr“拥有”的指针可以是任何东西,包括需要销毁的C ++类或结构(如在delete p中,其中p是对所述对象的void *)。 It can't know how to do that because shared_ptr only "knows" this to be a void *. 它不知道如何做到这一点,因为shared_ptr只“知道”这是一个空*。 It will only perform a delete of void *, and the destructor of that object will never be called. 它只会执行void *的删除,并且永远不会调用该对象的析构函数。 In order for that to be valid, you'd have to ensure only POD types (that don't require destructors be called) could be valid. 为了使其有效,您必须确保只有POD类型(不需要调用析构函数)才有效。 Simply put, for the context you're using, you can't use shared_ptr as a means of ensuring disposal of the memory, because it's not just freeing memory, it's destruction you must deal with. 简单地说,对于您正在使用的上下文,您不能使用shared_ptr作为确保处理内存的方法,因为它不仅仅是释放内存,而是您必须处理的破坏。

You could create copies of objects, which are not stored by void, but that means the map and multimap could not store just any object. 您可以创建对象的副本,这些副本不是由void存储的,但这意味着map和multimap不能存储任何对象。

This is the purpose of boost::any, but if you can't use that, you have to reconsider how to either handle destruction of the object(s) in the maps, or you have to limit storage to types that don't require destructors. 这是boost :: any的目的,但是如果你不能使用它,你必须重新考虑如何处理地图中对象的销毁,或者你必须将存储限制为不存在的类型需要析构函数。

There are too many potential solutions to the delimma to finalize a solution (I would be building the product for you, and making design choice for you to do that). 最终确定解决方案的解决方案有太多潜在的解决方案(我将为您构建产品,并为您做出设计选择)。

I can tell you the features required in the solution. 我可以告诉你解决方案所需的功能。

You must do away with shared_ptr. 你必须取消shared_ptr。 You can't rely on "automatic" freeing, which was your purpose in using shared_ptr. 你不能依赖“自动”释放,这是你使用shared_ptr的目的。 At destruction, you have no choice but to loop through all contained entries, cast them to their real types, and delete them "manually". 在销毁时,您别无选择,只需遍历所有包含的条目,将它们转换为真实类型,然后“手动”删除它们。 How you do that has myriad possibilities. 你是如何做到这一点有无数的可能性。

Your code has a few fundamental errors. 您的代码有一些基本错误。

First, you are using types deduced as forwarding references as if they where value types. 首先,您使用推导为转发引用的类型,就好像它们是值类型一样。 Args&&... are deduced forwarding references, which means Args could be a value or reference type. Args&&...导出转发引用,这意味着Args可以是值或引用类型。 std::tuple<Args> could then be a tuple of references. std::tuple<Args>可能是一个引用元组。 This is not what you want. 这不是你想要的。

Second, you are trying to avoid boost::any , and then you are reimplementing it wrong. 其次,你试图避免boost::any ,然后你重新实现它错了。 A boost::any is a void* and information about how to copy/destroy/cast it back to its original type. boost::any是一个void* 以及有关如何复制/销毁/将其强制转换回原始类型的信息。 Simply storing a void* won't do; 简单地存储void*行不通的; and storing a pointer to a variable of automatic storage (a stack variable) will be utter garbage. 并且存储指向自动存储变量(堆栈变量)的指针将是完全垃圾。

A distinct map for each type entry is tempting, but a decent program needs to be able to clear them. 每个类型条目的不同地图很诱人,但一个体面的程序需要能够清除它们。

Here is a .clear() type erasure view object. 这是一个.clear()类型的擦除视图对象。 It erases the action of calling .clear() on an object of arbitrary type: 它删除了对任意类型的对象调用.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);
  }
};

Now we can build a set of caches to clear, where the caches are different types: 现在我们可以构建一组要清除的缓存,其中缓存是不同的类型:

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

Now we need a way to automatically register the caches that are created. 现在我们需要一种方法来自动注册创建的缓存。 We also want to be able to look up "transparently", so we use std::less<void> for searching: 我们也希望能够“透明地”查找,因此我们使用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;
}

Finally: 最后:

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;
}

We can now add things to the cache. 我们现在可以将内容添加到缓存中。 We don't store references in the cache (unlike your version). 我们不在缓存中存储引用(与您的版本不同)。 We can clear every cache by calling clear_caches . 我们可以通过调用clear_caches来清除每个缓存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM