c++模板std::tuple为void*并返回

C++ templates std::tuple to void* and back

本文关键字:返回 void tuple 模板 std c++      更新时间:2023-10-16

我正在尝试使用c++ 11和可变模板制作资源管理器。问题是如何将std::tuple存储到集合中并将其取回?在这个例子中,我尝试将其存储为void*(尽量不在这里使用boost::any)。每次我将转换回std::tuple时,我都会得到转换后的元组与从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)...);
    }
};

为什么不对每个类型和参数使用一个函数局部映射呢?因为你已经通过这两个条件过滤了数据,它可以简化你的代码:

#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

在这一行:

 object.emplace(type, &currentArgs);

字符数组(或传入的任何类型)将在堆栈上。那不是你能拥有的存储。它不是通过任何指针分配使用和存储的,更不用说void *了,这意味着该指针的内容来自堆栈。

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

和后面的任何其他,堆栈在每次调用之前恰好处于相同的状态。这意味着当"bbb.jpg"被调用时," emplace "调用指向相同的内存,但现在已经变成"bbb.jpg"而不是"aaa.jpg"。由于堆栈在该程序的未来版本的其他地方使用,因此堆栈将随着程序的运行而变化,这意味着存储对象的内容将发生变化,似乎是随机的。

你必须做的是重新考虑存储。

您可以分配想要存储的对象的新副本,但这会带来另一个问题。您已经在ObjectCollection中存储了shared_ptr。它将不知道如何删除它。实际上,shared_ptr"拥有"的指针可以是任何东西,包括需要销毁的c++类或结构(如delete p,其中p是强制转换为所述对象的void *)。它不知道如何做到这一点,因为shared_ptr只"知道"这是一个void *。它将只执行void *的delete操作,并且永远不会调用该对象的析构函数。为了使其有效,您必须确保只有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来清除所有缓存。