C++ 模板 std::tuple 到 void* 并返回
C++ templates std::tuple to void* and back
我正在尝试使用 C++11 和可变参数模板制作资源管理器。问题是如何将 std::tuple 存储到集合中并取回?在此示例中,我尝试将其存储到 void*(此处尝试不使用 boost::any)。每次我转换回 std::tuple 时,我都会得到转换后的元组与从参数创建的元组相同 (currentArgs == storedArgs)。我认为下面的代码解释了一切。
#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, ¤tArgs);
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)...);
}
};
您正在存储指向函数局部变量的指针:
// declaration of local variable "currentArgs"
std::tuple<Args...> currentArgs(std::forward<Args>(args)...);
// ...
// storing the pointer of "currentArgs" in "object"
object.emplace(type, ¤tArgs);
该局部变量 (currentArgs
) 存在于堆栈中,从函数返回后指向它的指针将失效。巧合的是(因为你从同一个地方调用函数),下次调用函数时变量的地址完全相同,这意味着取消引用你的(无效的)指针解析为 currentArgs
的当前值.
为了避免这个问题,使用new
或make_shared
创建一个永久对象,并将指向它的原始指针或智能指针放在映射中object
。
这一行:
object.emplace(type, ¤tArgs);
字符数组(或传入的任何类型)将在堆栈上。那不是您可以拥有的存储空间。它不是通过任何指针分配使用和存储的,更不用说void *了,这意味着该指针的内容来自堆栈。
在这些行中的每次调用:
auto image1 = Resource::get<Image>("aaa.jpg");
auto image2 = Resource::get<Image>("bbb.jpg");
以及随后的任何其他调用,堆栈恰好在每次调用之前处于相同状态。这意味着当调用 "bbb.jpg" 时,'emplace' 调用指向相同的内存,但现在已变为 "bbb.jpg" 而不是 "aaa.jpg"。由于堆栈在该程序的未来版本中用于其他地方,因此堆栈将因 运行 程序而发生变化,这意味着存储对象的内容将发生变化,看似随机。
你必须做的是重新考虑存储。
您可以为要存储的对象分配一个新副本,但这会带来另一个问题。您已将 shared_ptr 存储在 ObjectCollection 中。它不知道如何删除它。实际上,shared_ptr 的指针 "owned" 可以是任何东西,包括 C++ class 或结构,这需要销毁(如在 delete p 中,其中 p 是对所述对象的 void * 转换).它不知道该怎么做,因为 shared_ptr 只有 "knows" this 是一个 void *。它只会执行 void * 的删除,并且永远不会调用该对象的析构函数。为了使其有效,您必须确保只有 POD 类型(不需要调用析构函数)才有效。简而言之,对于您正在使用的上下文,您不能使用 shared_ptr 作为确保处置内存的手段,因为它不仅仅是释放内存,它是您必须处理的破坏。
您可以创建不由 void 存储的对象的副本,但这意味着映射和多重映射不能只存储任何对象。
这是 boost::any 的目的,但如果不能使用它,则必须重新考虑如何处理地图中对象的破坏,或者必须限制存储到不需要析构函数的类型。
对于 delimma 的潜在解决方案太多,无法最终确定解决方案(我会为您构建产品,并为您做出设计选择)。
我可以告诉您解决方案中所需的功能。
你必须取消 shared_ptr。您不能依赖 "automatic" 释放,这是您使用 shared_ptr 的目的。在销毁时,您别无选择,只能遍历所有包含的条目,将它们转换为它们的真实类型,然后删除它们 "manually"。你如何做到这一点有无数的可能性。
为什么不为每个类型和参数使用一个函数局部映射?
由于您已经通过这 2 个条件过滤了数据,因此它可以简化您的代码:
#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。
您的代码有一些基本错误。
首先,您正在使用推断为转发引用的类型,就好像它们是值类型一样。 Args&&...
是推导的转发引用,这意味着 Args
可以是值或引用类型。 std::tuple<Args>
可以是一个引用元组。这不是你想要的。
其次,你试图避免boost::any
,然后你重新实现它是错误的。 boost::any
是 void*
和 有关如何 copy/destroy/cast 将其恢复为原始类型的信息。简单地存储 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();
}
现在我们需要一种方法来自动注册创建的缓存。我们还希望能够查找"transparently",所以我们使用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
.
来清除每个缓存
我正在尝试使用 C++11 和可变参数模板制作资源管理器。问题是如何将 std::tuple 存储到集合中并取回?在此示例中,我尝试将其存储到 void*(此处尝试不使用 boost::any)。每次我转换回 std::tuple 时,我都会得到转换后的元组与从参数创建的元组相同 (currentArgs == storedArgs)。我认为下面的代码解释了一切。
#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, ¤tArgs);
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)...);
}
};
您正在存储指向函数局部变量的指针:
// declaration of local variable "currentArgs"
std::tuple<Args...> currentArgs(std::forward<Args>(args)...);
// ...
// storing the pointer of "currentArgs" in "object"
object.emplace(type, ¤tArgs);
该局部变量 (currentArgs
) 存在于堆栈中,从函数返回后指向它的指针将失效。巧合的是(因为你从同一个地方调用函数),下次调用函数时变量的地址完全相同,这意味着取消引用你的(无效的)指针解析为 currentArgs
的当前值.
为了避免这个问题,使用new
或make_shared
创建一个永久对象,并将指向它的原始指针或智能指针放在映射中object
。
这一行:
object.emplace(type, ¤tArgs);
字符数组(或传入的任何类型)将在堆栈上。那不是您可以拥有的存储空间。它不是通过任何指针分配使用和存储的,更不用说void *了,这意味着该指针的内容来自堆栈。
在这些行中的每次调用:
auto image1 = Resource::get<Image>("aaa.jpg");
auto image2 = Resource::get<Image>("bbb.jpg");
以及随后的任何其他调用,堆栈恰好在每次调用之前处于相同状态。这意味着当调用 "bbb.jpg" 时,'emplace' 调用指向相同的内存,但现在已变为 "bbb.jpg" 而不是 "aaa.jpg"。由于堆栈在该程序的未来版本中用于其他地方,因此堆栈将因 运行 程序而发生变化,这意味着存储对象的内容将发生变化,看似随机。
你必须做的是重新考虑存储。
您可以为要存储的对象分配一个新副本,但这会带来另一个问题。您已将 shared_ptr 存储在 ObjectCollection 中。它不知道如何删除它。实际上,shared_ptr 的指针 "owned" 可以是任何东西,包括 C++ class 或结构,这需要销毁(如在 delete p 中,其中 p 是对所述对象的 void * 转换).它不知道该怎么做,因为 shared_ptr 只有 "knows" this 是一个 void *。它只会执行 void * 的删除,并且永远不会调用该对象的析构函数。为了使其有效,您必须确保只有 POD 类型(不需要调用析构函数)才有效。简而言之,对于您正在使用的上下文,您不能使用 shared_ptr 作为确保处置内存的手段,因为它不仅仅是释放内存,它是您必须处理的破坏。
您可以创建不由 void 存储的对象的副本,但这意味着映射和多重映射不能只存储任何对象。
这是 boost::any 的目的,但如果不能使用它,则必须重新考虑如何处理地图中对象的破坏,或者必须限制存储到不需要析构函数的类型。
对于 delimma 的潜在解决方案太多,无法最终确定解决方案(我会为您构建产品,并为您做出设计选择)。
我可以告诉您解决方案中所需的功能。
你必须取消 shared_ptr。您不能依赖 "automatic" 释放,这是您使用 shared_ptr 的目的。在销毁时,您别无选择,只能遍历所有包含的条目,将它们转换为它们的真实类型,然后删除它们 "manually"。你如何做到这一点有无数的可能性。
为什么不为每个类型和参数使用一个函数局部映射? 由于您已经通过这 2 个条件过滤了数据,因此它可以简化您的代码:
#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。
您的代码有一些基本错误。
首先,您正在使用推断为转发引用的类型,就好像它们是值类型一样。 Args&&...
是推导的转发引用,这意味着 Args
可以是值或引用类型。 std::tuple<Args>
可以是一个引用元组。这不是你想要的。
其次,你试图避免boost::any
,然后你重新实现它是错误的。 boost::any
是 void*
和 有关如何 copy/destroy/cast 将其恢复为原始类型的信息。简单地存储 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();
}
现在我们需要一种方法来自动注册创建的缓存。我们还希望能够查找"transparently",所以我们使用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
.