Reputation: 171
I don't understand how I can prevent accessing a dead object when it was created from a different scope and placed into some kind of container in another scope. Example
#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();
}
}
I have been trying for about 2 days to figure out some kind of "shared" pointer system that would wrap Foo*
with another object, but no matter what, it always comes down to the fact that even that "shared" pointer won't know when Foo
has died.. It's as if I am looking for a Foo
destructor callback.
Notes: C++ 98, no boost.
If this has been solved 1000 times already, I would just love to know the idea behind it so I can make an interpretation of it.
Edit: To add some more context
Essentially I have this recurring problem in my designs. I really like "loose coupling" and keeping modules separate. But in order for that to happen, there must be at least one place to connect them. So I go for the pub/sub or event based systems. Therefore there must be emitters and handlers. This actually just re-wraps the problem, though. If ModuleA
can emit events and ModuleB
can listen to events, than ModuleA
will have to have some kind of reference to ModuleB
. That's not so bad, but now we have to consider all the funkiness of scope, copy ctors, = operators, etc. We have no choice but to go full out.
Example
#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();
}
Things get worse if we had something called MyObject
that contains a component with an EventEmitter
of its own that we want to listen to (maybe a socket). If MyObject
is copied around, now we have this internal EventEmitter
with handlers that reference MyObject
that may no longer exist!
So maybe I dump it all and switch to callbacks.. But even then, some object is still owning ptrs or references to some other object that may no longer exist! We can be as careful as we want but we never know what's going to happen...
You know, I think what I need is to say this
Now linking objects together must be done through some higher level object that manages both of those objects...
...I should have stuck to graphic design.
Upvotes: 3
Views: 1527
Reputation: 171
I would like to put my own answer here to present the realization of what I was unknowingly trying to write: Garbage Collection. And more importantly, on memory that is already automatically managed.
That took about 3 days to understand. I was creating a system of nodes and references in order to keep objects alive. Literally garbage collection...
Woopsies.
Upvotes: 0
Reputation: 3419
You could use give the vector to the foo itself and remove the pointer in the destructor. Basically the object itself does the bookkeeping of pointers.
#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();
}
}
OUTPUT
➜ test ./a.out
length is 0
Upvotes: 0
Reputation: 30569
If you want an object's lifetime to extend beyond the scope in which it's created, then that object has to be created with dynamic lifetime. You use new
to do this:
std::vector<Foo*> foos;
{
Foo* foo = new Foo;
foos.push_back(foo);
}
The Foo
object that new
returns a pointer to will live until it is explicitly delete
d. Note that std::vector
will not do this for you. You must explicitly delete the objects pointed to by the pointers stored in your vector:
for (std::size_t i = 0; i < foos.size(); ++i) {
delete foos[i];
}
Ideally you would use a smart pointer of some kind to manage your dynamic-lifetime objects, but the standard smart pointers std::unique_ptr
and std::shared_ptr
were not in C++98. std::auto_ptr
was available, but that class is very easy to use incorrectly. It may be worth writing your own simple shared_ptr
-like class to do this for you. It's not too complicated if you don't need to support things like weak pointers and atomic operations. Here's a very basic implementation:
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_;
}
};
The basic premise is that all shared_ptr
s for a given object hold a pointer to the same control_block
. Whenever the shared_ptr
is copied its control_block
's reference count is incremented and whenever a shared_ptr
is destroyed its control_block
's reference count is decremented. If the reference count ever reaches zero it deletes the control block along with the pointed-to object.
Upvotes: 4
Reputation: 1072
In C++ you are responsible of life cycle of object. If you use
Foo foo;
foo will be destroyed at out of scope. So, you shouldn't use local variable. Use dynamic objects (for example):
Foo* foo = new Foo;
foos.push_back(foo);
It will work. And you are responsible for object destroying.
Upvotes: 3
Reputation: 15
Why don’t you use std::vector<Foo>
instead of std::vector<Foo*>
? In the former way, i think there’s no way to prevent Foo
object destroyed when it came out of scope - but when you push to vector
, it will sit there until program terminate or we explicit remove.
Upvotes: -2