我正在尝试使用std::unordered_map
来存储带有Resource
密钥的std::string
个对象。 Resource
实现类型擦除,以便构造函数Resource(objectOfAnyType)
创建一个Resource
对象,该对象包装传递给构造函数的对象的类型。
重要的是Resource
不被复制,因为它们可能包装不允许复制的对象,或者只支持浅复制,并且当它们被销毁时释放内存。所以我一直在尝试使用std::pair
分段构造函数,例如
std::unordered_map<std::string, MyStruct> umap;
umap.emplace(std::piecewise_construct, std::forward_as_tuple("tag"), std::forward_as_tuple(constructorArgs));
这很好用。当我尝试使用实现类型擦除的Resource
时,会出现问题。下面是一个完整(但简化)的例子,它重现了我遇到的问题。我意识到代码中存在几个不相关的问题(尤其是在MyStruct中对堆的所有可疑使用),但它不是生产代码,并且已经过大量编辑以通过更简单的示例重现问题。
#include <iostream>
#include <unordered_map>
#include <typeinfo>
#include <memory>
//Resource class providing a handle to generic game resources. Implements type erasure to support indefinite numbers of resource types.
class Resource
{
private:
//Allows mixed storage in STL containers.
class ResourceConcept
{
public:
virtual ~ResourceConcept() { }
};
//Templated model of ResourceConcept. Allows type erasure and storing of any type.
template <class ResourceType>
class ResourceModel : public ResourceConcept
{
private:
ResourceType modelledResource;
public:
ResourceModel(const ResourceType &_resource) : modelledResource(_resource) { }
virtual ~ResourceModel() { }
};
//Unique pointer to the resource itself. Points to an object of type ResourceConcept allowing any specific instantiation of the templated ResourceModel to be stored.
std::unique_ptr<ResourceConcept> resource;
//Uncommenting the two lines below causes an error, because std::pair is trying to copy Resource using operator=.
//Resource(const Resource* _other) = delete;
//Resource& operator= (const Resource&) = delete;
public:
//Constructor which initialises a resource with whichever ResourceModel is required.
template <class ResourceType>
Resource(const ResourceType& _resource) : resource(new ResourceModel<ResourceType>(_resource)) { std::cout << "Resource constructed with type " << typeid(_resource).name() << std::endl; }
};
//Example structure which we want to store/model as a Resource.
struct MyStruct
{
std::string* path;
MyStruct(std::string _path) : path(new std::string(_path)) { std::cout << "MyStruct constructor called..." << std::endl; }
~MyStruct() { std::cout << "MyStruct destructor called!" << std::endl; delete path; } //In a real example, this would deallocate memory, making shallow-copying the object a bad idea.
private:
MyStruct(const MyStruct* _other) = delete;
MyStruct& operator= (const MyStruct&) = delete;
};
int main()
{
std::unordered_map<std::string, Resource> umap;
std::string constructorArgs = "Constructor argument.";
//Store a MyStruct in the map using Resource(MyStruct) constructor...
umap.emplace(std::piecewise_construct, std::forward_as_tuple("tag1"), std::forward_as_tuple(constructorArgs)); //Calls Resource(std::string), which isn't what I want.
umap.emplace(std::make_pair("tag2", Resource(MyStruct(constructorArgs)))); //Calls a Resource(MyStruct), results in the MyStruct destructor being called twice! Example output below.
std::cout << "tag1: " << typeid(umap.at("tag1")).name() << "\ttag2: " << typeid(umap.at("tag2")).name() << std::endl;
std::cout << "End test." << std::endl;
/*
Example output:
Resource constructed with type Ss
MyStruct constructor called...
Resource constructed with type 8MyStruct
MyStruct destructor called! <--- I need to prevent this call to the destructor.
tag1: 8Resource tag2: 8Resource
End test.
MyStruct destructor called!
Segmentation fault
*/
return 0;
}
主要问题是MyStruct似乎被复制,一个副本被销毁。在实际代码中,这将导致指针被释放,而另一个副本带有悬空指针(因此seg错误)。
在使用类型擦除时是否可以构造就地/分段对?它是std :: move可以帮助的东西吗?一种解决方案可能是使用std::shared_ptr
,但如果我不必要地复制Resource
,我宁愿直接对其进行排序。
感谢您的帮助。
答案 0 :(得分:1)
如果您不想要复制资源,则应禁止在您的资源上使用。您尝试过但错误地说:您删除的副本构造函数(在MyStruct和Resource上)采用const指针,但它应该是const references,如
MyStruct(const MyStruct& _other) = delete;
Resource(const Resource& _other) = delete;
一旦你使用它,你就不再冒险使用segfault,因为MyStruct不能再被复制了,但你会得到一堆编译器错误,因为其余的代码想要复制。如您所述,输入移动构造函数,右值引用和std :: move:我建议您在网上或SO上阅读它,直到您完全理解它为止。
应用于您的特定代码,它需要进行以下更改:移动MyStruct的构造函数(最终也适用于Resource,但不会在您显示的代码中使用),并且Resource构造函数必须对资源进行rvalue引用,以便它可以从它移动:
MyStruct MyStruct&& rh) :
path(rh.path)
{
rh.path = nullptr;
}
template <class ResourceType>
Resource(ResourceType&& _resource) :
resource(new ResourceModel<ResourceType>(std::forward<ResourceType>(_resource)))
{
std::cout << "Resource constructed with type " << typeid(resource).name() << std::endl;
}
Resource(Resource&& rh) :
resource(std::move(rh.resource))
{
}
然后你只需使用
umap.emplace("tag2", Resource(MyStruct(constructorArgs)))