C ++如何知道对象何时被销毁
C++ How to know when an object has been destroyed
我不明白当一个死对象是从不同的范围创建并放入另一个范围的某种容器中时,我如何才能阻止访问它。例子
#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 次,我很想知道它背后的想法,这样我就可以对其进行解释。
编辑:添加更多上下文
基本上我的设计中有这个反复出现的问题。我真的很喜欢“松散耦合”并保持模块分离。但为了实现这一点,必须 至少有一个地方可以连接它们。所以我选择 pub/sub 或基于事件的系统。因此必须有发射器和处理程序。不过,这实际上只是重新包装了问题。如果 ModuleA
可以发出事件并且 ModuleB
可以监听事件,那么 ModuleA
将不得不对 ModuleB
有某种引用。这还不错,但现在我们必须考虑作用域、复制 ctors、= 运算符等的所有问题。我们别无选择,只能全力以赴。
例子
#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
的处理程序!
所以也许我将其全部丢弃并切换到回调..但即便如此,某些对象仍然拥有指针或对可能不再存在的其他对象的引用!我们可以尽可能小心,但我们永远不知道会发生什么...
你知道,我想我需要说的是这个
- 任何对象都不应该拥有对另一个对象的引用或指针,除非它自己创建了该对象。
现在必须通过管理这两个对象的某个更高级别的对象来将对象链接在一起...
...我应该坚持图形设计。
为什么不使用 std::vector<Foo>
而不是 std::vector<Foo*>
?在前一种方式中,我认为没有办法防止 Foo
对象在超出范围时被销毁 - 但是当你推送到 vector
时,它会一直坐在那里直到程序终止或我们明确删除。
在 C++ 中,您负责对象的生命周期。如果你使用
Foo foo;
foo 将在超出范围时被销毁。
所以,你不应该使用局部变量。
使用动态对象(例如):
Foo* foo = new Foo;
foos.push_back(foo);
它会起作用的。
而你负责对象的销毁。
如果您希望对象的生命周期超出其创建的范围,则必须使用动态生命周期创建该对象。您使用 new
来执行此操作:
std::vector<Foo*> foos;
{
Foo* foo = new Foo;
foos.push_back(foo);
}
new
returns 指针指向的 Foo
对象将一直存在,直到它被显式 delete
d。请注意,std::vector
不会 为您执行此操作。您必须显式删除存储在向量中的指针所指向的对象:
for (std::size_t i = 0; i < foos.size(); ++i) {
delete foos[i];
}
理想情况下,您会使用某种智能指针来管理您的 dynamic-lifetime 对象,但标准智能指针 std::unique_ptr
和 std::shared_ptr
不在 C++98 中。 std::auto_ptr
可用,但 class 非常 容易错误使用。可能值得编写您自己的简单 shared_ptr
-like class 来为您完成此操作。如果您不需要支持弱指针和原子操作之类的东西,它并不会太复杂。这是一个非常基本的实现:
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
的引用计数就会减少。如果引用计数达到零,它会删除控制块以及 pointed-to 对象。
您可以使用将向量赋予 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
我想把我自己的答案放在这里,以展示我在不知不觉中试图写的东西的实现:垃圾收集。更重要的是,在已经自动管理的内存上。
大概花了 3 天的时间才理解。我正在创建一个节点和引用系统,以使对象保持活动状态。字面上的垃圾收集...
糟糕。
我不明白当一个死对象是从不同的范围创建并放入另一个范围的某种容器中时,我如何才能阻止访问它。例子
#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 次,我很想知道它背后的想法,这样我就可以对其进行解释。
编辑:添加更多上下文
基本上我的设计中有这个反复出现的问题。我真的很喜欢“松散耦合”并保持模块分离。但为了实现这一点,必须 至少有一个地方可以连接它们。所以我选择 pub/sub 或基于事件的系统。因此必须有发射器和处理程序。不过,这实际上只是重新包装了问题。如果 ModuleA
可以发出事件并且 ModuleB
可以监听事件,那么 ModuleA
将不得不对 ModuleB
有某种引用。这还不错,但现在我们必须考虑作用域、复制 ctors、= 运算符等的所有问题。我们别无选择,只能全力以赴。
例子
#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
的处理程序!
所以也许我将其全部丢弃并切换到回调..但即便如此,某些对象仍然拥有指针或对可能不再存在的其他对象的引用!我们可以尽可能小心,但我们永远不知道会发生什么...
你知道,我想我需要说的是这个
- 任何对象都不应该拥有对另一个对象的引用或指针,除非它自己创建了该对象。
现在必须通过管理这两个对象的某个更高级别的对象来将对象链接在一起...
...我应该坚持图形设计。
为什么不使用 std::vector<Foo>
而不是 std::vector<Foo*>
?在前一种方式中,我认为没有办法防止 Foo
对象在超出范围时被销毁 - 但是当你推送到 vector
时,它会一直坐在那里直到程序终止或我们明确删除。
在 C++ 中,您负责对象的生命周期。如果你使用
Foo foo;
foo 将在超出范围时被销毁。 所以,你不应该使用局部变量。 使用动态对象(例如):
Foo* foo = new Foo;
foos.push_back(foo);
它会起作用的。 而你负责对象的销毁。
如果您希望对象的生命周期超出其创建的范围,则必须使用动态生命周期创建该对象。您使用 new
来执行此操作:
std::vector<Foo*> foos;
{
Foo* foo = new Foo;
foos.push_back(foo);
}
new
returns 指针指向的 Foo
对象将一直存在,直到它被显式 delete
d。请注意,std::vector
不会 为您执行此操作。您必须显式删除存储在向量中的指针所指向的对象:
for (std::size_t i = 0; i < foos.size(); ++i) {
delete foos[i];
}
理想情况下,您会使用某种智能指针来管理您的 dynamic-lifetime 对象,但标准智能指针 std::unique_ptr
和 std::shared_ptr
不在 C++98 中。 std::auto_ptr
可用,但 class 非常 容易错误使用。可能值得编写您自己的简单 shared_ptr
-like class 来为您完成此操作。如果您不需要支持弱指针和原子操作之类的东西,它并不会太复杂。这是一个非常基本的实现:
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
的引用计数就会减少。如果引用计数达到零,它会删除控制块以及 pointed-to 对象。
您可以使用将向量赋予 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
我想把我自己的答案放在这里,以展示我在不知不觉中试图写的东西的实现:垃圾收集。更重要的是,在已经自动管理的内存上。
大概花了 3 天的时间才理解。我正在创建一个节点和引用系统,以使对象保持活动状态。字面上的垃圾收集...
糟糕。