5

在下面的代码中,为什么会s1.printVal导致悬空指针错误?不是s1对象,即它的指针,在它被销毁之前仍然可以访问吗?

class Sample
{
  public:
    int *ptr;
    Sample(int i)
    {
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }
    void PrintVal()
    {
        cout << "The value is " << *ptr;
    }
};

void SomeFunc(Sample x)
{
    cout << "Say i am in someFunc " << endl;
}

int main()
{
    Sample s1 = 10;
    SomeFunc(s1);
    s1.PrintVal(); // dangling pointer
}

来源

4

4 回答 4

14

这里的问题SomeFunc(). 该副本在销毁时会取消分配您的指针。您还需要实现复制构造函数复制赋值运算符。见三法则

编辑:

这是“扩展的”伪代码,即编译器在main()函数中为您做了什么:

// main
addr0 = grab_stack_space( sizeof( Sample )); // alloc stack space for s1
Sample::ctor( addr0, 10 );                   // call ctor of Sample
addr1 = grab_stack_space( sizeof( Sample )); // alloc stack for argument
Sample::ctor( addr1, addr0 );                // call COPY-ctor of Sample
SomeFunc( addr1 );                           // call SomeFunc
Sample::dtor( addr1 );                       // XXX: destruct the copy
free_stack_space( addr1, sizeof( Sample ));  // free stack taken by copy
Sample::PrintVal( addr0 );                   // call member func on s1
Sample::dtor( addr0 );                       // destruct s1
free_stack_space( addr0, sizeof( Sample ));  // YYY: free stack taken by s1

不是确切的表示,而是概念上的解释。它只是帮助思考编译器与您的代码有什么关系。

的指针成员在标有 的步骤Sample被-ed ,然后在步骤 再次 -ed 。deleteXXXdeleteYYY

于 2010-08-12T14:01:53.980 回答
3

尼古拉的回答解释了一切,但这里有一个可能的选择:

如果您打算多个实例Sample共享相同的底层指针,您也可以使用类似的东西boost::shared_ptr而不是原始指针。

这有一点成本,但可能不会比你自己尝试做的多。

此外,这将避免编写任何复制构造函数、析构函数和赋值运算符。

于 2010-08-12T14:28:07.217 回答
2

当您调用SomeFunc(Sample x)时,对象x是通过调用Sample的复制构造函数来创建的。由于您没有显式编写一个,因此编译器会创建一个隐式的。通常,隐式的很好:它会逐个成员复制(如果您使用 avector<int>而不是int*您的代码将起作用)。但是,在这种情况下,这并不好。它只是复制 的值ptr,所以现在 x.ptr 和 s1.ptr 指向同一个 int[]。现在,当 SomeFunc 结束时,x被销毁,并在其上调用析构函数,意义ptr被删除。这释放了 使用的内存x,但由于它是相同的值,它也是 使用的内存s1

于 2010-08-12T14:09:24.813 回答
1

尼古拉的回答是绝对正确的。和ereOn's 一样。

您还需要考虑按值传递和按引用传递之间的区别。

如果 SomeFunc 被声明为:

void SomeFunc(Sample& x)

甚至更好

void SomeFunc(const Sample& x)

你不会有悬空指针问题。

按照您定义的方式SomeFuncSample对象是按值传递的,这意味着制作了一个临时副本以供在SomeFunc. 然后,当SomeFunc返回临时对象超出范围并调用其析构函数时,它会删除指向的整数ptr

如果您传递对Sample对象的引用,则在调用时不会生成任何副本SomeFunc,因此在SomeFunc返回时不会调用析构函数。

于 2010-08-12T14:56:54.400 回答