检查复制构造函数中的“自我赋值”?

时间:2011-04-01 19:03:56

标签: c++ class copy-constructor

今天在大学我被一位教授推荐我在复制构造函数中检查(this != &copy),类似于在重载operator=时应该如何做。但是我质疑这一点,因为我无法想到在构造对象时this与参数相等的任何情况。

他承认我提出了一个很好的观点。所以,我的问题是,执行此检查是否有意义,或者这不可能搞砸了?

编辑:我想我是对的,但我会暂时保持开放状态。也许有人想出一些疯狂的神秘c ++魔法。

Edit2 Test a(a)编译MinGW,但不编译MSVS10。 Test a = a编译两者,所以我假设gcc的行为有点类似。不幸的是,VS 显示调试消息“变量a未经初始化使用。但它确实会为int i = i正确显示此消息。这真的可以被认为是c ++语言的缺陷吗?

class Test
{
   Test(const Test &copy)
   {
      if (this != &copy) // <-- this line: yay or nay?
      {
      }
   }
   Test &operator=(const Test &rhd)
   {
      if (this != &rhd) // <-- in this case, it makes sense
      {
      }
   }
};

9 个答案:

答案 0 :(得分:9)

这是有效的C ++并调用复制构造函数:

Test a = a;

但这没有任何意义,因为a在初始化之前就被使用了。

答案 1 :(得分:9)

就个人而言,我认为你的教授是错的,这就是原因。

当然,代码会编译。当然,代码被破坏了。但是,就你的教授推理他而言,他的结论是“哦,我们应该看看我们是否自我分配,如果我们是,那就回来吧。”

但这很糟糕,因为同样的原因,为什么拥有一个无所事事的全球性的所有catch(...)都是邪恶的。您正在阻止直接问题,但问题仍然存在。代码无效。您不应该使用指向self的指针调用构造函数。解决方案不是忽视问题而继续。解决方案是修复代码。可能发生的最佳事情是你的代码会立即崩溃。 最差的事情是代码将在一段时间内继续处于无效状态,然后再崩溃(当调用堆栈对你没有好处时),或者生成无效输出。

不,你的教授错了。在不检查自我分配的情况下完成任务。在代码审查中查找缺陷或让代码崩溃并在调试会话中找到它。但是,不要只是继续,好像什么也没发生过。

答案 2 :(得分:5)

如果你想成为偏执狂,那么:

class Test
{
   Test(const Test &copy)
   {
       assert(this != &copy);
       // ...
   }
};

如果this == &copy,你永远不想继续。我从来没有打扰过这张支票。在我使用的代码中似乎不经常出现错误。但是,如果您的经验不同,那么断言可能是值得的。

答案 3 :(得分:4)

你的导师可能正试图避免这种情况 -

#include <iostream>

class foo
{
    public:
    foo( const foo& temp )
    {
        if( this != &temp )
        std::cout << "Copy constructor \n";
    }
};

int main()
{
    foo obj(obj);  // This is any how meaning less because to construct
                   // "obj", the statement is passing "obj" itself as the argument

}

由于名称(即obj)在时间声明中可见,因此代码编译并有效。

答案 4 :(得分:3)

您的教师可能会考虑在复制作业操作员中检查自我分配。

建议在Sutter和Alexandrescu的“C ++编码标准”和Scott Meyer早期的“Effective C ++”中检查赋值运算符中的自我赋值。

答案 5 :(得分:1)

在正常情况下,似乎没有必要这样做。但请考虑以下情况:

class A{ 
   char *name ; 
public:
   A & operator=(const A & rhs);
};

A & A::operator=(const A &rhs){
   name = (char *) malloc(strlen(rhs.name)+1);
   if(name) 
      strcpy(name,rhs.name);
   return *this;
}

显然,当我们进行自我分配时,上面的代码存在问题。在我们复制内容之前,指向原始数据的指针将丢失,因为它们都引用相同的指针。这就是我们需要检查自我分配的原因。功能应该像

A & A::operator=(const A &rhs){
     if(this != &rhs){
       name = (char *) malloc(strlen(rhs.name)+1);
       if(name) 
          strcpy(name,rhs.name);
     } 
   return *this;
}

答案 6 :(得分:0)

在编写赋值运算符和复制构造函数时,始终执行此操作:

struct MyClass
{
    MyClass(const MyClass& x)
    {
        // Implement copy constructor. Don't check for
        // self assignment, since &x is never equal to *this.
    }

    void swap(MyClass& x) throw()
    {
        // Implement lightweight swap. Swap pointers, not contents.
    }

    MyClass& operator=(MyClass x)
    {
        x.swap(*this); return *this;
    }
};

按值x传递给赋值运算符时,会生成一个副本。然后将其与*this交换,并在返回时调用x的析构函数,使用旧值*this。简单,优雅,异常安全,无代码重复,无需自我分配测试。

如果您还不了解异常,在学习异常安全时可能需要记住这个习惯用法(现在忽略throw()的{​​{1}}说明符)。

答案 7 :(得分:0)

我同意自我检查在复制构造函数中没有任何意义,因为尚未创建对象但您的教授正确地添加检查以避免任何进一步的问题。我试过/没有自我检查,如果没有自检和运行时错误,如果存在自检,我会得到意想不到的结果。

 Test t2(t2);
 t2.display(); 

当创建复制构造函数和被调用的成员函数时,我没有

    Test(const Test& obj )
    {
        if(this != &obj )
        {
            size = obj.size;
            a = new int[size];
        }
    }

<强>输出:
内部默认构造函数
内部参数化构造函数
内部复制构造函数
构造函数有效

这在句法上可能是正确的,但看起来不正确。 通过自检,我得到运行时错误,将错误指向代码,以避免这种情况。

[AuthenticationController.IsLoggedInAsHero]
public ActionResult Hero()
{
    return View();
}

运行时错误:
`/ home / bot / 1eb372c9a09bb3f6c19a27e8de801811'出错:munmap_chunk():无效指针:0x0000000000400dc0

答案 8 :(得分:0)

通常,operator =和copy构造函数调用复制函数,因此可能会发生自我赋值。 所以,

Test a;
a = a;

例如,

const Test& copy(const Test& that) {
    if (this == &that) {
        return *this
    }
    //otherwise construct new object and copy over
}
Test(const &that) {
    copy(that);
}

Test& operator=(const Test& that) {
    if (this != &that) { //not checking self 
        this->~Test();
    }
    copy(that);
 }

在上面,当执行a = a时,调用操作符重载,调用copy函数,然后检测自我赋值。