【问题标题】:Copy constructor functionality复制构造函数功能
【发布时间】:2016-02-27 10:00:24
【问题描述】:

我正在尝试理解复制构造函数的概念。我用了这个例子:

#include <iostream>

using namespace std;

class Line
{
public:
    int GetLength();
    Line(int len);
    Line(const Line &obj);
    ~Line();

private:
    int *ptr;
};

Line::Line(int len)
{
    ptr = new int;
    *ptr = len;
};

Line::Line(const Line &obj)
{
    cout << "Copying... " << endl;
    ptr = new int;
    *ptr = *obj.ptr;
};

Line::~Line()
{
    delete ptr;
};

int Line::GetLength()
{
    return *ptr;
}

int main()
{
    Line line1 = Line(4);
    cout << line1.GetLength() << endl;

    Line line2 = line1;
    line1.~Line();

    cout << line2.GetLength() << endl;

    return 0;
}

问题是,为什么会出现运行时错误?如果我定义了一个为新 ptr 分配内存的复制构造函数,并将 line1 分配给 line2,这是否意味着这两个是单独的对象?通过破坏line1,我显然也搞砸了line2,还是我使用的析构函数调用错误?

【问题讨论】:

  • 实现赋值运算符也是一个好习惯。但是在您的情况下,问题是显式的析构函数调用,您的指针将被删除两次,这会导致运行时错误。
  • 你为什么要这么做line1.~Line();?谁让你这么做的?
  • 这只是为了练习,我尝试这个看看当我破坏 line1 时 line2 对象会发生什么。我来自 C# 世界:)
  • @omegasbk 但随后它被破坏了两次
  • 当我们超出范围时?

标签: c++


【解决方案1】:

你在这个语句中调用了析构函数

line1.~Line();

删除分配给ptr的内存

Line::~Line()
{
    delete ptr;
};

但是对象line1 是活动的,因为它具有自动存储持续时间。因此,在退出 main 之后,对象的析构函数将被再次调用,结果它将尝试删除 ptr 指向的已被明确删除的内存。

【讨论】:

  • 从技术上讲,一旦第二次调用析构函数([class.dtor]/15),它就是未定义的行为
【解决方案2】:
line1.~Line();

仅当您使用 placement new 时,手动调用析构函数才有用。

我不明白是什么让你想到在这个程序中手动调用析构函数。当您还是该语言的新手时,您并不想真正了解这种低级内存管理机制,但为了完整起见,它的工作方式如下:

int main()
{
    // provide static memory with enough space for one Line object:
    char buffer[sizeof(Line)];

    // create a Line object and place it into buffer:
    Line* line1 = new (buffer) Line(4);

    cout << line1->GetLength() << endl;

    Line line2 = *line1;

    // manually call the destructor:
    line1->~Line();

    cout << line2.GetLength() << endl;

    // - no delete necessary because buffer disappears automatically
    // - no automatic destructor call

    return 0;
}

但是,您的代码会导致尝试调用 line1 的析构函数两次。首先手动,然后在对象的范围结束时自动,即在main 的末尾。这是未定义的行为。见Does explicitly calling destructor result in Undefined Behavior here?

问题是,为什么会出现运行时错误?

因为未定义的行为意味着您的程序可以做或不做任何事情。不保证运行时错误,也不保证任何确定性行为。

【讨论】:

  • 我认为手动触发析构函数会导致释放对象持有的内存。当我将 line1 分配给 line2 时,我想确保我有一个新对象。
  • @omegasbk:析构函数永远不会释放自己对象占用的内存。那不是他们的目的。 “析构函数”这个词本身可能令人困惑;从技术上讲,它应该被命名为“do_stuff_before_object_is_destructed_by_someone_else”。
  • 啊哈,有道理!谢谢!
  • @omegasbk:C++ 语言中的其他此类有趣的观察:delete 没有删除它的参数,std::move 没有移动,std::forward 没有转发:)
猜你喜欢
  • 2013-10-13
  • 2013-01-10
  • 1970-01-01
  • 1970-01-01
  • 2015-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多