在堆栈上声明的类对象与其他堆栈变量具有相同的生命周期吗?
我有这段代码:
#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;
}
我的问题:
family
中,是否在堆栈中声明了Parent p
?从查看输出看起来似乎是Child
也会进入堆栈,对吗?Child
实例时,我向它传递一个指向堆栈变量的指针。我想这是一个 big no-no ,因为堆栈变量只能保证直到函数结束。之后,堆栈应该弹出,变量将被销毁。这是对的吗?vector.push_back()
passes arguments by reference,所以在family
函数的末尾,p.children
只包含对局部变量的引用,对吗?main
中,为什么我可以访问父级及其子级?是不是因为来自family
的局部变量仍然完好无损并且未被某些后续函数调用覆盖? 我认为我误解了C ++中存在于内存中的东西。我真的很想成为一个解释得很好的资源。提前谢谢。
修改
编译源并运行的输出:
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'
答案 0 :(得分:4)
这一切都有效,因为vector
复制了push_back
的所有内容。您的family
函数也返回一个副本,因此即使堆栈变量p
超出范围并被销毁,该副本也是有效的。
我应该指出Parent
对象保留的Child
指针在family
函数结束后无效。由于您没有在Child
中显式创建复制构造函数,因此编译器会自动为您生成一个复制构造函数,并且它会直接复制指针; p
超出范围时,指针将指向无效对象。
答案 1 :(得分:2)
向量中的Child对象因Mark Ransom指出的原因而存活,但每个Child包含的指向Parent *的指针(指向p)变得无效,正如您所期望的那样。
如果它似乎工作,可能发生的是编译器的优化器内联family(),然后组合main(){p}
和family(){p}
的存储以避免复制返回的对象。这种优化甚至可能没有内联,但几乎可以肯定。
很容易理解为什么在这种情况下允许它,因为你的Parent
类不会自定义复制构造函数,但实际上它是允许的。 C ++标准特别引用return value optimization,并允许编译器假装复制构造函数没有副作用,即使它无法证明这一点。
要解决此问题,需要在堆上分配Parent,并且需要进行一些其他规定来释放它。假设没有涉及时间旅行(因此没有对象可以成为它自己的祖先),这可以通过使用tr1 :: shared_ptr(或boost::shared_pointer用于TR1之前的编译器)来轻松实现每个孩子持有的指针到它的父母。
答案 2 :(得分:1)
是的,没错。但是,由于很明显函数系列返回了p,编译器将使用它来存储结果,而不是实际将其复制到Parent p = family();
的左侧。换句话说,它不会在family()中创建p然后复制它,因为这会浪费。相反,它在main()中创建p并将其用作family()中的p以直接存储结果(避免无用的副本)。
不,std::vector
动态分配内存来存储其元素(由大小可以在运行时更改的事实表示)。因此,推送到向量容器的Child实例存储在动态分配的内存(Heap)中。
是的,这是正确的。你应该避免这种情况,因为它可能是不安全的。避免这种情况并且仍然能够在Child对象中存储指向Parent的指针的一种好方法是使Parent不可复制(使复制构造函数和赋值运算符都是私有的而没有实现)。这将产生这样的效果:由于父项不能被复制,并且由于父项包含其子项,因此只要子项未被销毁(因为它们与父项一起被销毁),子项指向父项的指针将永远不会失效。 。此方案通常还会为Child对象提供一种工厂函数,并在Child的构造函数上提供私有访问权限,以便为父级或静态工厂函数授予友谊。这样,也可以禁止程序员创建Child的实例,这些实例不是由它们所附着的父项直接拥有的。另请注意,移动语义和/或深度复制可以使父级“可复制”或至少可移动,同时保持子级与其父级一致。
vector.push_back()
通过引用传递参数,因此在系列函数的末尾,p.children只包含对局部变量的引用,对吗?不,vector通过const
引用获取参数,然后可能为该对象分配额外的存储空间,然后将参数的副本放入新的内存插槽(placement new
运算符)。所以p.children
是对象(不是引用)并且包含在vector
中(毕竟它被称为“容器”)。
如果你读了我的第一个答案,很明显为什么这仍然有效(但它可能不会一直有效)。