问题描述
我对 C++ 还很陌生,刚刚了解了智能指针。现在我想知道确保它们被有效使用的最佳实践是什么(假设我的优化算法需要创建数亿个对象,[在 relatives
] 中进行评估,然后安全地丢弃 - 理想情况下以并发方式)。
如果您能告诉我这是否可以,或者我是否应该做一些不同/更好的其他事情,我将不胜感激。
编辑:这是一个简化的示例,重点关注对象的正确创建。显然,简单的属性可以用不同的方式表示。在这里,我试图专注于学习如何正确创建引用其他对象的对象,并且在销毁它们之前可能会被许多其他对象引用。所以我的问题是,下面的代码是否是一种相当快速、更重要、更安全的创建对象的方法。
class Property {
public:
int id;
};
class Pet {
const int id_;
const std::shared_ptr<Property> myProperty_;
const std::shared_ptr<Property> myProperty2_;
std::map<double,std::shared_ptr<Pet>> relatives; // one of the places where the object will be referenced,I use "HashMap" in my Java prototype
Pet(const int id,const std::shared_ptr<Property> myProperty,const std::shared_ptr<Property> myProperty2) :
id_(id),myProperty_(myProperty),myProperty2_(myProperty2) { }
const std::shared_ptr<Pet> makePet(const int id,const std::shared_ptr<Property> myProperty2) {
return std::make_shared<Pet>(id,myProperty,myProperty2);
}
};
正如我之前提到的,最终我希望能够实现一些东西,以便多个线程可以访问 relatives
,但不要认为这与对象创建相关(是吗?)。
先谢谢你!
解决方法
std::shared_ptr
的概念非常酷,它出现后我不得不在我的第一个新项目中到处使用它!当拥有 std::shared_ptr
的对象被销毁时,它为所有指向被销毁的对象启用 RAII(一种 C++ 显式垃圾收集方法)。
现在 std::shared_ptr
有一些缺点,一些是概念上的,一些是实现上的。
- 有可能产生循环依赖,使程序的其余部分无法引用数据,从而有效地泄漏内存。
- 如果只有一个逻辑所有者,则有一个更简单的智能指针,
std::unique_ptr
- 如果存在非拥有引用,则可以使用原始指针代替。 (非所有者必须在拥有智能指针之前销毁)
- 它引入了额外的间接寻址。
std::shared_ptr<Pet> pet;
pet->control block->Pet object
这是一个外部引用计数,而不是引用计数是对象的一部分。
pet->Counter + Pet object
因此,如果您的目标是在没有更多引用的情况下删除宠物,则可以采用不同的方法来实现。
using PetId = int;
std::unordered_map<PetId,std::shared_ptr<Pet>> PetDict; // global pet owner,hash map/dictionary
void ErasePet(PetId id) {
auto pet = PetDict.find(id);
if (pet != PetDict.end()) // can be avoided if you know id is in PetDict ... no,you can't be sure.
PetDict.erase(pet); // this is not enough as the relatives still keep the pet alive and will cause a dead pet to still have relations while not being findable anymore.
}
因此,在删除宠物之前,您必须让 relatives
忘记宠物,至少有三种方法可以做到这一点
- 将
relatives
改为使用std::weak_ptr
- 在使用
relatives
的元素之前需要额外检查,因为关系可能已被删除。
- 在使用
- 更改
relatives
以使用PetId
而不是std::shared_ptr<Pet>
- 每次使用
relatives
的元素之前都需要在 PetDict 中进行额外查找,因为关系可能已被删除。
- 每次使用
- 遍历
relatives
以删除每个中的关系。- 因为
relatives
没有被PetId
索引,所以relatives
的所有节点都必须检查正确的PetId
,相当可怕的 O(m) 但是那么多久 ar宠物被删除了?
- 因为
前两个选项是一种惰性删除,因为一旦发现它不再处于活动状态,它们就可以删除该关系,尽管它仍然会在每次使用时进行活动检查。
关于线程安全,有几种变体。
- 如果你只在创建宠物后阅读,一切都很好
- 如果您通过复制
std::shared_ptr
来更改引用计数也是可以的,因为计数器是std::shared_ptr
的唯一原子部分。 - 如果在控制块中更改
std::shared_ptr
指向的对象,则会出现竞争条件 - 如果更改
std::shared_ptr
指向的对象的值,则会出现竞争条件。
在 C++20 中,std::atomic(std::shared_ptr) 可以帮助控制块的原子更新,但您仍然需要自己访问指向的对象线程安全。 必须评估多少读取/写入的通常成本收益。