繁体   English   中英

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

[英]Check for "self-assignment" in copy constructor?

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

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

Edit2Test a(a)在 MinGW 上编译,但不是 MSVS10。 Test a = a两者都编译,所以我认为 gcc 的行为会有些相似。 不幸的是,VS没有显示带有"Variable a used without being initialized"的调试消息。 但是,它确实正确显示了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
      {
      }
   }
};

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

当然,代码会编译。 当然,代码被破坏了。 但这只是你的教授的推理,然后他得出结论:“哦,好吧,我们应该看看我们是否在自我分配,如果是,就回来。”

但这很糟糕,因为同样的原因,为什么拥有一个什么都不做的全局包罗万象的catch(...)是邪恶的。 您正在防止立即出现问题,但问题仍然存在。 代码无效。 您不应该使用指向 self 的指针调用构造函数。 解决方案不是忽略问题并继续前进。 解决方案是修复代码。 可能发生的最好的事情是您的代码会立即崩溃。 最糟糕的是,代码会在一段时间内继续处于无效状态,然后要么稍后崩溃(当调用堆栈对你没有好处时),要么生成无效输出。

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

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

Test a = a;

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

如果你想偏执,那么:

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

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

您的教练可能正试图避免这种情况 -

#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 )在时间声明时可见,因此代码编译并有效。

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

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;
}

您的教师可能正在考虑在复制分配运算符中检查自分配。

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

我同意自我检查在复制构造函数中没有任何意义,因为尚未创建对象,但您的教授关于添加检查是正确的,只是为了避免任何进一步的问题。 我尝试使用/不使用自检并在没有自检时得到意外结果,如果存在自检,则出现运行时错误。

class Test
{    
    **public:**
    Test(const Test& obj )
    {
       size = obj.size;
       a = new int[size];   
    }

    ~Test()
    {....}

    void display()
    {
        cout<<"Constructor is valid"<<endl;
    }
    **private:**

 }

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

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

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

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

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

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

一般operator=和拷贝构造函数调用拷贝函数,所以有可能发生自赋值。 所以,

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 时,调用运算符重载,它调用复制函数,然后检测自赋值。

在编写赋值运算符和复制构造函数时,请始终这样做:

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的旧值。 简单、优雅、异常安全、无代码重复、无需自赋值测试。

如果您还不了解异常,您可能希望在学习异常安全时记住这个习惯用法(暂时忽略掉用于swapthrow()说明符)。

编写对自赋值安全的复制赋值运算符是C++ 核心指南,并且有充分的理由。 意外地陷入自我分配的情况比这里的一些讽刺评论所暗示的要容易得多,例如在迭代 STL 容器时没有考虑太多:

std::vector<Test> tests;
tests.push_back(Test());
tests.resize(10);
for(int i = 0; i < 10; i++)
{
  tests[i] = tests[0]; // self when i==0
}

这段代码有意义吗? 并不真地。 是否可以通过粗心或稍微复杂的情况轻松编写? 当然。 这是错的吗? 不是真的,但即使是这样......惩罚应该是段错误和程序崩溃吗? 哎呀,不。

要构建不会因为愚蠢的原因而出现段错误的健壮类,只要不使用复制和交换习惯用法或其他安全替代方法,就必须测试自分配。 无论如何,一个人可能应该选择复制和交换,但有时不这样做是有道理的,性能明智。 了解自分配测试模式也是一个好主意,因为它以大量遗留代码显示。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM