我有一个Entity类,其中包含特定类型实体的各种子类。子类的对象彼此之间具有各种关系,因此我使用Entity指针和static_cast来描述这些关系,并允许实体访问与其有关系的其他实体的信息。
然而,我发现使用原始指针是很多困难的来源。难以调试错误。例如,我浪费了几个小时才意识到我在将对象复制到向量之前指向对象,这使得指针无效。
我正在使用矢量和列表&像这样的集合来管理我的所有内存 - 我真的试图避免担心内存管理和低级别问题。
Say Cow是Entity的子类,我有一个Cows(或Cows的一些数据结构)的向量。如果需要调整大小或其他内容,此向量可能会在内存中移动。我真的不想关心这些细节,但我知道它们可能会使我的指针无效。因此,如果一只猪与一头母牛有关系并且牛的矢量被调整大小,那么猪就会失去其朋友在记忆中所处位置的轨迹。有一个危险的错误指针。
以下是我的问题,虽然我认为有经验的人可能只是通过阅读情况来帮助我...
答案 0 :(得分:3)
据我所知,你正在做这样的事情:
cow florence, buttercup;
std::vector<cow> v = { florence, buttercup };
cow* buttercup_ptr = &v[1];
// do something involving the cow*
你的buttercup_ptr
正在失效,因为,例如。 florence
已从矢量中移除。
可以通过使用智能指针解决此问题,如下所示:
std::shared_ptr<cow> florence = std::make_shared<cow>();
std::vector<std::shared_ptr<cow>> v;
v.push_back(florence);
你现在可以自由地分享florence
...关于矢量如何被改组的问题,它仍然有效。
如果你想让florence
在向量中弹出时被摧毁,那就会出现一个小问题:任何持有shared_ptr
副本的人都会阻止此cow
实例被清理干净。您可以使用weak_ptr
来避免这种情况。
std::weak_ptr<cow> florence_ref = florence;
要使用weak_ptr
,请在其上调用lock()
将其转换为完整shared_ptr
。如果底层的cow
实例已经被销毁,因为它没有强引用,那么抛出异常(当然,你也可以在expired()
之前调用lock()
来检查它!)
另一种可能性(正如我在原始问题的评论中所建议的)是使用指向容纳cow
实例的容器的迭代器。这有点陌生,但迭代器在许多方面都非常像指针。你必须小心选择容器,以确保你的迭代器不会失效(这意味着你不能使用vector
来实现这个目的);
std::set<cow> s;
cow florence;
auto iter = s.insert(florence);
// do something with iter, and not worry if other cows are added to
// or removed from s
我认为我不一定会建议这个选项,但我相信它会起作用。使用智能指针是向未来维护程序员展示您的意图的更好方法,并且具有获取悬挂引用更加困难的优势。
答案 1 :(得分:2)
你对指针有误解。指针(为了讨论!)只是一个地址。为什么复制地址会使任何内容失效?假设您的对象是在地址12345
的内存中创建的。因此,new
会为您提供一个值为12345
的指针:
Entity *entity = new Cow; // object created at address 12345
// entity now has value 12345
稍后,当您将该指针复制到向量(或任何其他标准容器)中时,除了值12345
之外,不会复制任何内容。
std::vector<Entity *> entities;
entities.push_back(entity);
// entities[0] now has value 12345, entity does not change and remains 12345
事实上,与std::vector<int>
相比,这方面没有区别:
int i = 12345;
std::vector<int> ints;
ints.push_back(i);
// ints[0] now has value 12345, i does not change and remains 12345
复制向量时,里面的值不会改变:
std::vector<int> ints2 = ints;
std::vector<Entity *> entities2 = entities;
// i, ints[0] and ints[0] are 12345
// entity, entities[0] and entities[0] are 12345
因此,如果猪与母牛有关系,那么母牛的载体就是 调整大小后,猪会失去其朋友在记忆中的位置 &安培;有一个危险的错误指针。
这种担心是没有根据的。猪不会 失去对其朋友的追踪。
危险的是保持指向已删除对象的指针:
delete entity;
entities[0]->doSometing(); // undefined behaviour, may crash
如何解决这个问题是C ++中成千上万个问题,网站,博客和书籍的主题。智能指针可能对您有所帮助,但您应该首先真正理解所有权的概念。
答案 2 :(得分:2)
我的两分钱。下面的代码是一个工作示例(C ++ 11,用g ++ 4.8.2翻译),带有智能指针和工厂函数create
。应该是非常安全的。 (如果你真的想要,你总是可以在C ++中销毁一些东西。)
#include <memory>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>
class Entity;
typedef std::shared_ptr<Entity> ptr_t;
typedef std::vector<ptr_t> animals_t;
class Entity {
protected:
Entity() {};
public:
template<class E, typename... Arglist>
friend ptr_t create(Arglist... args) {
return ptr_t(new E(args...));
}
animals_t my_friends;
virtual std::string what()=0;
};
class Cow : public Entity {
double m_milk;
Cow(double milk) :
Entity(),
m_milk(milk)
{}
public:
friend ptr_t create<Cow>(double);
std::string what() {
std::ostringstream os;
os << "I am a cow.\nMy mother gave " << m_milk << " liters.";
return os.str();
}
};
class Pig : public Entity {
Pig() : Entity() {}
public:
friend ptr_t create<Pig>();
std::string what() {
return "I am a pig.";
}
};
int main() {
animals_t animals;
animals.push_back(create<Cow>(1000.0));
animals.push_back(create<Pig>());
animals[0]->my_friends.push_back(animals[1]);
std::for_each(animals.begin(),animals.end(),[](ptr_t v){
std::cout << '\n' << v->what();
std::cout << "\nMy friends are:";
std::for_each(v->my_friends.begin(),v->my_friends.end(),[](ptr_t f){
std::cout << '\n' << f->what();
});
std::cout << '\n';
});
}
/*
Local Variables:
compile-command: "g++ -std=c++11 test.cc -o a.exe && ./a.exe"
End:
*/
通过将Entity构造函数声明为受保护的构造函数,强制必须通过工厂函数create
创建Entity类型的派生对象。
工厂确保对象始终进入智能指针(在我们的例子中为std::shared_ptr
)。
它被写为模板,使其可以在子类中重用。更确切地说,它是一个variadic template,允许构造函数在派生类中包含任意数量的参数。在程序的修改版本中,将母牛的产奶量作为构造函数参数传递给新创建的奶牛。