【问题标题】:Check for "self-assignment" in copy constructor?检查复制构造函数中的“自赋值”?
【发布时间】:2023-04-03 05:22:01
【问题描述】:

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

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

编辑:我想我是对的,但我会暂时把它打开。也许有人想出了一些疯狂的神秘 C++ 魔法。

Edit2Test a(a) 在 MinGW 上编译,但不是 MSVS10。 Test a = a 两者都编译,所以我认为 gcc 的行为会有些相似。不幸的是,VS 确实显示带有“未初始化的变量使用”的调试消息。但是,它确实为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
      {
      }
   }
};

【问题讨论】:

  • 我想这一定是你教授的愚人节玩笑。
  • std::string x(x);的可能重复
  • 您的教授可能不掌握 C++,不应该教它。如果您使用的是复制和交换习语,则几乎从不需要检查自分配。 [我说“几乎”是因为它是(过早的)优化的一种形式。]。如果你不知道复制和交换习语,你必须学习它,并且学习不要写那些令人毛骨悚然的代码重复自分配测试内存泄漏异常不安全15行长赋值运算符。
  • @dialer:为什么会有缺陷?我认为这没有任何问题。
  • @ybungalobill 因为它使您能够创建一个对象(第二个a)而无需为其调用任何构造函数(甚至不是默认构造函数)。然后,当您复制未构造的对象时,您所有的 RAII 努力都将被搞砸。更糟糕的是,如果不进一步检查,代码甚至不会崩溃,直到销毁原始的 &amp;copy 对象。当然,这些分配只不过是错误,但 IMO 也应该将它们视为 错误,K&R C 已经。甚至复制和交换也可能搞砸设备句柄等。

标签: c++ class copy-constructor


【解决方案1】:

就个人而言,我认为你的教授错了,原因如下。

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

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

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

【讨论】:

  • 这也是std::string x(x) 的答案所建议的。我明白你在说什么,但不幸的是它没有崩溃。
  • 是的,我刚刚获得了所有这些答案。
  • 我认为教授是对的,尽管他可能不知道为什么他是对的。考虑使用placement new调用对象的构造函数的情况。
【解决方案2】:

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

Test a = a;

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

【讨论】:

  • 在语法上是有效的,但在初始化之前使用该值仍然无效。
  • @BoPersson:它不一定使用任何未初始化的值。 IE。复制构造函数可以在不从参数中读取任何内容的情况下将 a 设置为 0,或者它可能是一个空类……在这种情况下,它将以任何可能的方式从标准 POV 中有效。
【解决方案3】:

如果你想偏执,那么:

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

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

【讨论】:

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 我也向他指出了这一点,这实际上让他思考了一下。不过,他并不完全确定复制构造函数是否需要它。
      • 如果他真的不确定,我会拿他教的所有东西加几粒盐。这是非常基本的 C++ 内容。
      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

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

        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() 说明符)。

        【讨论】:

        • 永远不要说总是。也不要说从不。 :-) 这个答案 (stackoverflow.com/questions/5072082/…) 描述了一个流行的类 (std::vector),它不会从实现为“复制和交换”的分配中受益。
        • @Howard:当您开始使用 C++ 时,最好说“总是进行复制和交换”,至少对于代码不重复和异常安全而言。您描述的示例(无论如何感谢它)是相当做作的,并且涉及线程安全,这是另一个蠕虫包。
        【解决方案8】:

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

        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

        【讨论】:

          【解决方案9】:

          一般来说,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被执行时,操作符重载被调用,它调用复制函数,然后检测自赋值。

          【讨论】:

            猜你喜欢
            • 2012-02-12
            • 1970-01-01
            • 2016-09-13
            • 1970-01-01
            • 2013-06-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多