在堆栈上创建类对象

时间:2011-02-04 03:55:01

标签: c++

在堆栈上声明的类对象与其他堆栈变量具有相同的生命周期吗?

我有这段代码:

#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'

3 个答案:

答案 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?从查看输出看起来似乎是

是的,没错。但是,由于很明显函数系列返回了p,编译器将使用它来存储结果,而不是实际将其复制到Parent p = family();的左侧。换句话说,它不会在family()中创建p然后复制它,因为这会浪费。相反,它在main()中创建p并将其用作family()中的p以直接存储结果(避免无用的副本)。

  • 每个创建的Child也会进入堆栈,对吧?

不,std::vector动态分配内存来存储其元素(由大小可以在运行时更改的事实表示)。因此,推送到向量容器的Child实例存储在动态分配的内存(Heap)中。

  • 当我创建每个Child实例时,我向它传递一个指向堆栈变量的指针。我想这是一个很大的禁忌,因为堆栈变量只能保证直到函数结束。之后,堆栈应该弹出,变量将被销毁。这是对的吗?

是的,这是正确的。你应该避免这种情况,因为它可能是不安全的。避免这种情况并且仍然能够在Child对象中存储指向Parent的指针的一种好方法是使Parent不可复制(使复制构造函数和赋值运算符都是私有的而没有实现)。这将产生这样的效果:由于父项不能被复制,并且由于父项包含其子项,因此只要子项未被销毁(因为它们与父项一起被销毁),子项指向父项的指针将永远不会失效。 。此方案通常还会为Child对象提供一种工厂函数,并在Child的构造函数上提供私有访问权限,以便为父级或静态工厂函数授予友谊。这样,也可以禁止程序员创建Child的实例,这些实例不是由它们所附着的父项直接拥有的。另请注意,移动语义和/或深度复制可以使父级“可复制”或至少可移动,同时保持子级与其父级一致。

  • vector.push_back()通过引用传递参数,因此在系列函数的末尾,p.children只包含对局部变量的引用,对吗?

不,vector通过const引用获取参数,然后可能为该对象分配额外的存储空间,然后将参数的副本放入新的内存插槽(placement new运算符)。所以p.children是对象(不是引用)并且包含在vector中(毕竟它被称为“容器”)。

  • 为什么一切正常?主要的,为什么我可以访问父母及其每个孩子?是不是因为来自family的局部变量仍然完好无损并且未被某些后续函数调用覆盖?

如果你读了我的第一个答案,很明显为什么这仍然有效(但它可能不会一直有效)。

相关问题