问题描述
我不明白当从另一个作用域创建一个死对象并将其放入另一个作用域的某种容器中时,如何防止访问该死对象。例子
#include <iostream>
#include <string>
#include <vector>
class Foo {
public:
void Speak(){
std::cout << "ruff";
}
};
int main()
{
std::vector<Foo*> foos;
{
Foo foo;
foos.push_back(&foo);
// foo is now dead
}
for(size_t i = 0; i < foos.size(); i++){
// uh oh
foos[i]->Speak();
}
}
我已经尝试了大约2天,以找出某种“共享”指针系统,该系统将Foo*
与另一个对象包装在一起,但是无论如何,它总是归结为这样一个事实,即使是“共享”指针将不知道Foo
何时死亡。好像我在寻找Foo
析构函数回调一样。
注意:C ++ 98,没有增强。
如果这个问题已经解决了1000次,我只想知道它背后的想法,以便对它进行解释。
编辑:添加更多上下文
基本上,我的设计中经常遇到这个问题。我真的很喜欢“松散耦合”并保持模块分离。但是,为了做到这一点,必须至少有一个地方将它们连接起来。因此,我选择了基于发布/订阅或事件的系统。因此,必须有发射器和处理程序。但是,这实际上只是解决了问题。如果ModuleA
可以发出事件并且ModuleB
可以侦听事件,则ModuleA
将必须具有对ModuleB
的某种引用。并不是很糟糕,但是现在我们必须考虑范围,复制ctor,=运算符等的所有时髦之处。我们除了全力以赴之外,别无选择。
示例
#include <iostream>
#include <string>
class Handler {
void HandleEvent();
};
class Emitter {
public:
// add handler to some kind of container
void AttachHandler(Handler *handler);
// loop through all handlers and call HandleEvent
void EmitEvent();
};
int main()
{
// scope A
Emitter emitter;
// scope B
{
Handler handler;
emitter.AttachHandler(handler);
}
// rats..
emitter.EmitEvent();
}
如果我们有一个名为MyObject
的东西,它包含一个我们自己想听的EventEmitter
的组件(可能是一个套接字),情况会变得更糟。如果复制了MyObject
,现在我们将拥有内部EventEmitter
,其中包含引用了MyObject
的处理程序,该处理程序可能不再存在!
因此,也许我将其全部转储并切换到回调。。但是即使那样,某些对象仍然拥有ptrs或对可能不再存在的其他对象的引用!我们可以根据自己的意愿小心谨慎,但我们永远不知道会发生什么...
你知道,我想我需要这样说
- 除非对象自己创建了该对象,否则任何对象都不得引用或引用该对象。
现在将对象链接在一起必须通过管理这两个对象的更高级别的对象来完成...
...我应该坚持平面设计。
解决方法
如果要使对象的生存期超出创建对象的范围,则必须使用动态生存期来创建该对象。您使用new
执行此操作:
std::vector<Foo*> foos;
{
Foo* foo = new Foo;
foos.push_back(foo);
}
Foo
返回指针的new
对象将一直存在,直到明确delete
d为止。请注意,std::vector
将不会为您执行此操作。您必须明确删除存储在向量中的指针所指向的对象:
for (std::size_t i = 0; i < foos.size(); ++i) {
delete foos[i];
}
理想情况下,您将使用某种智能指针来管理动态生命周期对象,但是标准智能指针std::unique_ptr
和std::shared_ptr
不在C ++ 98中。 std::auto_ptr
可用,但是该类非常容易错误使用。可能需要编写自己的类似shared_ptr
的简单类来为您完成此操作。如果您不需要支持弱指针和原子操作之类的工具,它并不太复杂。这是一个非常基本的实现:
template <typename T>
class shared_ptr
{
private:
struct control_block
{
control_block(T* ptr)
: ref_count_(1),ptr_(ptr)
{}
~control_block()
{
delete ptr_;
}
size_t ref_count_;
T* ptr_;
};
control_block* control_block_;
public:
shared_ptr()
: control_block_(NULL)
{}
shared_ptr(T* ptr)
: control_block_(new control_block(ptr))
{}
shared_ptr(const shared_ptr& other)
: control_block_(other.control_block_)
{
++control_block_->ref_count_;
}
shared_ptr& operator=(shared_ptr other)
{
std::swap(control_block_,other.control_block_);
return *this;
}
~shared_ptr()
{
if (control_block_) {
--control_block_->ref_count_;
if (control_block_->ref_count_ == 0) {
delete control_block_;
}
}
}
T& operator*() { return *control_block_->ptr_; }
T* operator->() { return control_block_->ptr_; }
bool operator==(const shared_ptr& other)
{
return control_block_ == other.control_block_;
}
};
基本前提是,给定对象的所有shared_ptr
都拥有指向同一control_block
的指针。每当复制shared_ptr
时,其control_block
的参考计数就会增加,而无论何时shared_ptr
被破坏时,其control_block
的参考计数都会减少。如果引用计数曾经达到零,它将与指向的对象一起删除控制块。
在C ++中,您负责对象的生命周期。如果您使用
Foo foo;
foo将被破坏而超出范围。 因此,您不应使用局部变量。 使用动态对象(例如):
Foo* foo = new Foo;
foos.push_back(foo);
它将起作用。 您负责破坏对象。
,您可以使用将向量赋给foo本身,并删除析构函数中的指针。基本上,对象本身负责指针的簿记。
#include <iostream>
#include <string>
#include <vector>
class Foo {
std::vector <Foo *> *v;
public:
Foo(std::vector <Foo *> *v): v(v){}
void Speak(){
std::cout << "ruff";
}
~Foo() {
auto it = std::find(v->begin(),v->end(),this);
if (it != v->end())
{
v->erase(it);
}
}
};
int main()
{
std::vector<Foo*> foos;
{
Foo foo(&foos);
foos.push_back(&foo);
// foo is now dead
}
std::cout << "length is " << foos.size() << '\n';
for(size_t i = 0; i < foos.size(); i++){
// uh oh
foos[i]->Speak();
}
}
输出
➜ test ./a.out
length is 0
,
我想在这里提出自己的答案,以表达我在不知不觉中试图写的内容:垃圾收集。而且更重要的是,在已经自动管理的内存上。
花了大约三天的时间才能理解。我正在创建一个由节点和引用组成的系统,以使对象保持活动状态。从字面上看是垃圾收集...
糟糕。
,为什么不使用std::vector<Foo>
而不是std::vector<Foo*>
?以以前的方式,我认为无法防止Foo
对象超出范围时被破坏-但是当您推动vector
时,它将一直坐在那里直到程序终止或我们显式删除。 / p>