Moderators: phlip, Moderators General, Prelates
Yakk wrote:You are addicted to a garbage collector and your compiler maintaining the lifetime of your variables.
C++ Variable Lifetime 101:
There are at least four kinds of variable lifetimes in C++.
Stack
Heap
Global
Static Local
Variables of each type have a different lifetime. The lifetime of a variable is the time during which you can legally access the variable -- reading it afterwards or before that lifetime is illegal and results in undefined behaviour.
(In theory, a compiler could notice undefined behaviour, then read your web browser cache, extract your credit card numbers, and buy 1000 benie babies to be shipped to Egypt, and still follow the standard. However, it usually means that you will get random behaviour, sometimes it might work, other times it might crash your program.)
Stack: Items of stack, or automatic, lifetime exist only during the scope in which they have been created. An example of a stack lifetime is:
- Code: Select all
int* foo() {
int bar = 7;
return 0;
}
The variable "bar" only exists during the squigly braces {} -- after that, it no longer exists. If your function foo() returned a pointer to bar, using that pointer would result in undefined behaviour.
You are actually doing that in the last bit of code you posted.
The Stack is a stack. When you call a function, the variables are pushed onto the stack, and then the function reads the variables off the stack. The local variables in the function are also placed on the stack. Stack variables are nice because of how simple they are to determine lifetime -- they allocate faster than the alternatives, and go away after a bounded period of time.
Heap:
The Heap is what you get when you call new. Heap objects have a dynamic lifetime -- their lifetime is determined by your code logic. You get rid of a Heap variable by calling delete.
There is a parallel C heap system using malloc/free/calloc. You should avoid using both in the same program.
The heap is a pool of memory blocks that is searched whenever you call new. Each new takes a non-zero time to run. You want to minimize the calls to new when running kernel-level code and when running "per pixel" code (ie, code that must run millions of times per second) at the current level of computer power, but outside of such situations new is pretty cheap.
Keeping track of the lifetime of heap variables is a hard problem. You must figure out when the last pointer to a heap variable goes away, and call delete on it. You must never reference heap data after it has been deleted.
Global:
This data is brought into being at the start of program execution, before main() runs, and is cleaned up after main() ends. The order it is brought into being and goes away is a bit unpredictable, so it sucks in that way. Plus it never goes away, even if you are finished using it. Global variables are an example of global program state. Ideally, you want the state of each function in your application to be describable and understandable by a human -- and every bit of global state makes this harder and harder and less likely to happen.
Static Local:
This is very much like global state, but the variable is first brought into being when the function is first called. This can cause all sorts of race condition and initialization order problems, and solve all sorts of race condition and initialization problems.
In general, both Global and Static Local data causes problems when your program becomes threaded, or is extended in ways you didn't predict.
...
That help?
adlaiff6 wrote:So what undefined pointer are you saying I am returning? temp? I think there was a reason I declared that inside the for loop rather than outside.
typedef struct Link{
...
};typedef struct s_Link{
...
}Link;Yakk wrote:auto_ptr is junk.
Really.
Teaspoon wrote:worries me. Aren't you meant to do "typedef <definition> <name>;"?
- Code: Select all
typedef struct Link{
...
};or summat.
- Code: Select all
typedef struct s_Link{
...
}Link;
If you declare a struct without a typedef ("struct foobar{int bar;int foo;};) then you'll need to use "struct foobar" as the type when you create your variables and whatnot, so you put the typedef in to map some other name to the "struct foobar".
Then again, Wikipedia tells me that C++ automatically generates a typedef with a matching name when you declare a struct. I'm posting this anyway to point out something you need to watch out for in C.
struct ref_loop {
typedef ref_loop my_type;
const my_type* mutable next;
const my_type* mutable prev;
ref_loop():next(this), prev(this){}
ref_loop(ref_loop const& other):next(this), prev(this) {couple(&other);}
// joins us to other's loop
void couple(const my_type* other) {
const my_type* const next_ = other->next;
const my_type* const prev_ = other;
next = next_;
prev = prev_;
next->prev = this;
prev->next = this;
}
// returns true iff we are last element in our loop
bool uncouple() const {
if (next == this) {
return true;
}
const my_type* const next_ = next;
const my_type* const prev_ = prev;
next_->prev = prev_;
prev_->next = next_;
next = prev = this;
return false;
}
};
template<typename T>
struct c_ref:ref_loop {
T* data;
template<typname U>
c_ref(U* data_):data(data_){}
~c_ref() {if (uncouple()) delete data; data=0}
template<typename U>
my_type(const c_ref<U>& other) {
couple(&other);
data=other.data;
}
template<typename U>
my_type const& operator=(c_ref<U> const& other) {
my_type tmp = other;
if (uncouple()) delete data; data = 0;
couple(tmp);
data=tmp.data;
return *this;
}
T& operator*()const {return *data;}
T* operator->()const {return data;}
T* operator T* const {return data;}
};
Yakk wrote:I know what auto_ptr is for. But the problem is, it isn't very good at it.
It does prevent leaks to a certain degree,
but it implicitly creates and imposes a third kind of operator= / copy constructor -- the destructive move operator.
yy2bggggs wrote:but it implicitly creates and imposes a third kind of operator= / copy constructor -- the destructive move operator.
auto_ptr's do not have copy constructors.
#include <memory>
using std::auto_ptr;
int main() {
auto_ptr<int> x;
auto_ptr<int> y(x);
}EvanED wrote:Sure it does:
Yakk is right though that the copy constructor and op= work differently than they do virtually everywhere else.
There are plenty of cases where the auto_ptr works wonderfully, but it does have a number of shortcomings (e.g. can't use it in a container) that reference counted pointers don't, and it seems like the latter have very few drawbacks other than the small overhead of doing the reference counts.
yy2bggggs wrote:But they have their uses--in fact, they're quite useful at doing what they are for--representing single ownership. The times where you need single ownership of an object are not at all rare.
There are plenty of cases where the auto_ptr works wonderfully, but it does have a number of shortcomings (e.g. can't use it in a container) that reference counted pointers don't, and it seems like the latter have very few drawbacks other than the small overhead of doing the reference counts.
Not to mention leaking memory if there's ever a cycle of references via shared_ptr's. That's no reason not to use shared_ptr, but it's worth pointing out that auto_ptr and shared_ptr both warrant caution, but in practice they're both easy to handle.
Yakk wrote:auto_ptr with a disabled operator= and copy constructor is a decent object.
But taking standard operators and making them act in alien ways is a very bad thing.
It is analagous to making a+b copy the value of a onto b, but worse: copy constructors and operator= are implicitly called by the compiler.
If you want a nice, safe auto_ptr-type construct, write a shared_ptr that asserts that it is the last owner of an object when it goes out of scope. This provides better feedback in the debugging stage than auto_ptrs, allows you to create temporary references that are tracked to make sure they go away, and if you miss a rare case that makes it to release reduces the amount of undefined behaviour you execute.
evilbeanfiend wrote:it is still worrying however that the data seams to be simultaneously in a linked list and an array - surely not right?
adlaiff6 wrote:evilbeanfiend wrote:it is still worrying however that the data seams to be simultaneously in a linked list and an array - surely not right?
It's an array of linked lists, called an adjacency list (hence the variable name). Standard data structure for graphs.
The operator= and copy constructors are implemented in the only possible way to implement them for single ownership pointers. In practice, the problems they present are very easily avoided.
auto_ptr<T>::return_value GetAutoPtr(); // ap<T>::rv has copy as move
auto_ptr<T> x = GetAutoPtr(); // works
auto_ptr<T> y = x; // error
Don't get me wrong -- I think that C++ needs a destructive move operator. It would be useful in massive numbers of cases, from implementing an auto_ptr, to returning std::vectors from functions.
Yakk wrote:Projects of any significant size are not written by single programmers.
And if you are a perfect programmer, you don't need auto_ptr -- you can manage it manually.
What makes constructs like auto_ptr useful is that they make enforcing the contract the programmer wants enforced easier.
You could just disable them, and provide methods that do the destructive-move operation (possibly using temporary intermediate objects as helpers in some cases).
(Rename to auto_ptr2 mine).You could just disable them, and provide methods that do the destructive-move operation (possibly using temporary intermediate objects as helpers in some cases).
...
- Code: Select all
auto_ptr2<T>::return_value GetAutoPtr(); // ap<T>::rv has copy as move
auto_ptr2<T> x = GetAutoPtr(); // works
auto_ptr2<T> y = x; // error
Yakk wrote:That pImpl? You just created a class that has a the wonkey auto_ptr assigment semantics.
And if you aggregate, inherit, or otherwise cause the =/copy it happens automatically.
And if you use the null-value as a flag for "the empty value" or the like, your code doesn't crash. It just behaves strangely in a place that is far from the actual point where the mistake was made.
The strange behaviour is far, far divorced from the actual mistake: using auto_ptr without disabling your class's copy constructor.
Writing an auto_ptr that doesn't support copy-construct (copy-construct simply fails) is easy, and it results in a better class.
It doesn't live up to my standard for namespace std, at the very least.
In comparison, vector/list/deque/string kick ass (and string has issues too: note that next to nobody implements lazy-copy strings. I just use non-mutable strings.)
Users browsing this forum: GuetraGma and 12 guests