Creating class objects on the stack
Do class objects declared on the stack have the same lifetime as other stack variables?
I have this code:
#include <stdio.h>
#include <vector>
using std::vector;
#include <string>
using std::string;
class Child;
class Parent
{
public:
Parent(string s) : name(s) { };
vector<Child> children;
string name;
};
class Child
{
public:
Child() { /* I need this for serialization */ };
Child(Parent *p) : parent(p) { };
Parent *parent;
};
Parent
family()
{
Parent p("John Doe");
int i;
printf("family:\n\tparent: 0x%x\n\ti: %x\n", &p, &i);
for (i = 0; i < 2; ++i)
p.children.push_back(Child(&p));
return p;
}
int
main(void)
{
Parent p = family();
printf("main:\n\tparent: 0x%x\n", &p);
for (unsigned int i = 0; i < p.children.size(); ++i)
printf
(
"\t\tchild[%d]: parent: 0x%x parent.name '%s'\n",
i,
p.children[i].parent,
p.children[i].parent->name.c_str()
);
return 0;
}
My questions:
- In function
family
, isParent p
declared on the stack? From looking at the output, it would seem so - Each created
Child
goes on the stack too, right? - When I create each
Child
instance, I pass it a pointer to a stack variable. I imagine this is a big no-no, because stack variables are guaranteed to live only until the end of the function. After that the stack should get popped and the variables will be de开发者_如何学Pythonstroyed. Is this correct? vector.push_back()
passes arguments by reference, so at the end of thefamily
function,p.children
just contains references to the local variables, right?- Why is it all working? In
main
, why can I access the parent and each of its children? Is it all because the local variables fromfamily
are still intact and haven't been overwritten by some subsequent function call?
I think I'm misunderstanding where stuff lives in memory in C++. I'd really like to be pointed a resource that explains it well. Thanks in advance.
EDIT
Output from compiling the source and running:
misha@misha-K42Jr:~/Desktop/stackoverflow$ ./a.out
family:
parent: 0x2aa47470
i: 2aa47438
main:
parent: 0x2aa47470
child[0]: parent: 0x2aa47470 parent.name 'John Doe'
child[1]: parent: 0x2aa47470 parent.name 'John Doe'
It all works because vector
makes copies of everything that you push_back
. Your family
function is also returning a copy, so even though the stack variable p
goes out of scope and gets destroyed, the copy is valid.
I should point out that the Parent
pointers retained by the Child
objects will be invalid after the end of the family
function. Since you didn't explicitly create a copy constructor in Child
, one was generated for you automatically by the compiler, and it does a straight copy of the pointer; the pointer will point to an invalid object once p
goes out of scope.
The Child objects that are in the vector survive for the reason that Mark Ransom pointed out, but the pointer to Parent * that each Child contains (which points to p) becomes invalid just as you expected.
If it appears to work, what likely happend is the compiler's optimizer inlined family(), and then combined the storage of main(){p}
and family(){p}
to avoid copying the returned object. This optimization would be likely even without inlining, but nearly certain with it.
It's easy to see why it would be allowed in this case, since your Parent
class doesn't customize the copy constructor, but it's actually allowed regardless. The C++ standard makes special reference to return value optimization, and permits the compiler to pretend that a copy constructor has no side effects, even if it can't prove this.
To fix this, the Parent needs to be allocated on the heap, and some other provision would need to be made to free it. Assuming that no time-travel is involved (so that no object can become its own ancestor), this could be easily accomplished by using tr1::shared_ptr (or boost::shared_pointer for pre-TR1 compilers) for the pointer each child holds to its parent.
- In function family, is Parent p declared on the stack? From looking at the output, it would seem so
Yes, that's right. However, since it is clear that p is returned by the function family, the compiler will use it to store the result instead of actually copying it into left-hand-side of Parent p = family();
. In other words, it doesn't create the p in family() and then copies it because that would be wasteful. Instead, it creates the p in main() and uses it as p in family() to store the result directly (avoiding the useless copy).
- Each created Child goes on the stack too, right?
No, std::vector
dynamically allocates memory to store its elements (as indicated by the fact that the size can change at run-time). So the instances of Child that are pushed to the vector container are store in dynamically allocated memory (the Heap).
- When I create each Child instance, I pass it a pointer to a stack variable. I imagine this is a big no-no, because stack variables are guaranteed to live only until the end of the function. After that the stack should get popped and the variables will be destroyed. Is this correct?
Yes that is correct. You should avoid this situation because it can be unsafe. One good way to avoid this and still have the capability of storing a pointer to the Parent in the Child object is to make the Parent non-copyable (making both copy-constructor and assignment operator private and without an implementation). This will have the effect that since a Parent cannot be copied and since the parent contains its children, the pointer to parent that the children have will never go invalid as long as the children are not destroyed (since they are destroyed along with their parent). This scheme would usually also come with a sort-of factory function for the Child objects and a private access on the Child's constructor granting friendship to the parent or static factory function. That way, it is also possible to prohibit a programmer from creating instances of Child that are not directly owned by the parent to which they are attached. Note also, that move-semantics and/or deep-copying can make the parent "copyable" or at least movable while keeping the children consistent with their parent.
vector.push_back()
passes arguments by reference, so at the end of the family function, p.children just contains references to the local variables, right?
No, vector takes arguments by const
reference, then possibly allocates additional storage for that object, and then places a copy of the argument into that new memory slot (placement new
operator). So the p.children
are objects (not references) and are contained in the vector
(it is called a "container" after all).
- Why is it all working? In main, why can I access the parent and each of its children? Is it all because the local variables from family are still intact and haven't been overwritten by some subsequent function call?
If you read my first answer, it becomes evident why this still works (but it might not work all the time).
精彩评论