简体   繁体   English

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

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

Today in university I was recommended by a professor that I'd check for (this != &copy) in the copy constructor, similarly to how you should do it when overloading operator= .今天在大学里,一位教授推荐我在复制构造函数中检查(this != &copy) ,类似于重载operator=时应该如何做。 However I questioned that because I can't think of any situation where this would ever be equal to the argument when constructing an object.但是我质疑这一点,因为我想不出任何情况下this会等于构造对象时的论点。

He admitted that I made a good point.他承认我的观点很好。 So, my question is, does it make sense to perform this checking, or is it impossible that this would screw up?所以,我的问题是,执行此检查是否有意义,或者这不可能搞砸?

Edit : I guess I was right, but I'll just leave this open for a while.编辑:我想我是对的,但我会让这个打开一段时间。 Maybe someone's coming up with some crazy cryptic c++ magic.也许有人想出了一些疯狂的神秘 C++ 魔法。

Edit2 : Test a(a) compiles on MinGW, but not MSVS10. Edit2Test a(a)在 MinGW 上编译,但不是 MSVS10。 Test a = a compiles on both, so I assume gcc will behave somewhat similar. Test a = a两者都编译,所以我认为 gcc 的行为会有些相似。 Unfortunately, VS does not show a debug message with "Variable a used without being initialized" .不幸的是,VS没有显示带有"Variable a used without being initialized"的调试消息。 It does however properly show this message for int i = i .但是,它确实正确显示了int i = i的此消息。 Could this actually be considered a c++ language flaw?这实际上可以被认为是一个 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
      {
      }
   }
};

Personally, I think your professor is wrong and here's why.就个人而言,我认为您的教授是错误的,这就是原因。

Sure, the code will compile.当然,代码会编译。 And sure, the code is broken.当然,代码被破坏了。 But that's as far as your Prof has gone with his reasoning, and he then concludes "oh well we should see if we're self-assigning and if we are, just return."但这只是你的教授的推理,然后他得出结论:“哦,好吧,我们应该看看我们是否在自我分配,如果是,就回来。”

But that is bad, for the same reason why having a global catch-all catch(...) which does nothing is Evil.但这很糟糕,因为同样的原因,为什么拥有一个什么都不做的全局包罗万象的catch(...)是邪恶的。 You're preventing an immediate problem, but the problem still exists.您正在防止立即出现问题,但问题仍然存在。 The code is invalid.代码无效。 You shouldn't be calling a constructor with a pointer to self.您不应该使用指向 self 的指针调用构造函数。 The solution isn't to ignore the problem and carry on.解决方案不是忽略问题并继续前进。 The solution is to fix the code.解决方案是修复代码。 The best thing that could happen is your code will crash immediately.可能发生的最好的事情是您的代码会立即崩溃。 The worst thing is that the code will continue in an invalid state for some period of time and then either crash later (when the call stack will do you no good), or generate invalid output.最糟糕的是,代码会在一段时间内继续处于无效状态,然后要么稍后崩溃(当调用堆栈对你没有好处时),要么生成无效输出。

No, your professor is wrong.不,你的教授错了。 Do the assignment without checking for self-assignment.在不检查自我分配的情况下完成分配。 Find the defect in a code review or let the code crash and find it in a debug session.在代码审查中找到缺陷,或者让代码崩溃并在调试会话中找到它。 But don't just carry on as if nothing has happened.但不要只是继续,好像什么都没发生过。

This is valid C++ and calls the copy constructor:这是有效的 C++ 并调用复制构造函数:

Test a = a;

But it makes no sense, because a is used before it's initialized.但这没有任何意义,因为a是在初始化之前使用的。

If you want to be paranoid, then:如果你想偏执,那么:

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

You never want to continue if this == &copy .如果this == &copy ,你永远不想继续。 I've never bothered with this check.我从来没有为这张支票烦恼过。 The error doesn't seem to frequently occur in the code I work with.在我使用的代码中似乎并不经常发生该错误。 However if your experience is different then the assert may well be worth it.但是,如果您的经验不同,那么断言可能是值得的。

Your instructor is probably trying to avoid this situtation -您的教练可能正试图避免这种情况 -

#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

}

Since the name ( ie, obj ) is visible at the time declaration, the code compiles and is valid.由于名称(即obj )在时间声明时可见,因此代码编译并有效。

In normal situations, it seems like here is no need to.在正常情况下,这里似乎没有必要。 But consider the following situation:但考虑以下情况:

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

Obviously the code above has an issue in the case when we are doing self assignment.显然,在我们进行自赋值的情况下,上面的代码存在问题。 Before we can copy the content, the pointer to the original data will be lost since they both refer to same pointer.在我们可以复制内容之前,指向原始数据的指针将丢失,因为它们都引用同一个指针。 And that is why we need to check for self assignment.这就是为什么我们需要检查自我分配。 Function should be like功能应该像

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

Your instructor may be thinking of the check for self-assignment in the copy assignment operator.您的教师可能正在考虑在复制分配运算符中检查自分配。

Checking for self-assignment in the assignment operator is recommended, in both Sutter and Alexandrescu's "C++ Coding Standards," and Scott Meyer's earlier "Effective C++."在 Sutter 和 Alexandrescu 的“C++ 编码标准”以及 Scott Meyer 早期的“Effective C++”中都建议检查赋值运算符中的自赋值。

I agree that self check doesn't make any sense in copy constructor since object isn't yet created but your professor is right about adding the check just to avoid any further issue.我同意自我检查在复制构造函数中没有任何意义,因为尚未创建对象,但您的教授关于添加检查是正确的,只是为了避免任何进一步的问题。 I tried with/without self check and got unexpected result when no self check and runtime error if self check exists.我尝试使用/不使用自检并在没有自检时得到意外结果,如果存在自检,则出现运行时错误。

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

    ~Test()
    {....}

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

 }

When created copy constructor and called member function i didn当创建复制构造函数并调用成员函数时,我没有

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

Output:输出:
Inside default constructor内部默认构造函数
Inside parameterized constructor内部参数化构造函数
Inside copy constructor内部复制构造函数
Constructor is valid构造函数有效

This may be correct syntactically but doesn't look right.这在语法上可能是正确的,但看起来不正确。 With self check I got runtime error pointing the error in code so to avoid such situation.通过自我检查,我得到了运行时错误,指出代码中的错误,以避免这种情况。

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

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

Generally, the operator= and copy constructor calls a copy function, so it is possible that self-assignment occurs.一般operator=和拷贝构造函数调用拷贝函数,所以有可能发生自赋值。 So,所以,

Test a;
a = a;

For example,例如,

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

Above, when a = a is executed, the operator overload is called, which calls the copy function, which then detects the self assignment.上面,当执行 a = a 时,调用运算符重载,它调用复制函数,然后检测自赋值。

When writing assignment operators and copy constructors, always do this:在编写赋值运算符和复制构造函数时,请始终这样做:

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

When passing x by value to the assignment operator, a copy is made.x按值传递给赋值运算符时,会生成一个副本。 Then you swap it with *this , and let x 's destructor be called at return, with the old value of *this .然后你将它与*this交换,并在返回时调用x的析构函数,并使用 * *this的旧值。 Simple, elegant, exception safe , no code duplication, and no need for self assignment testing.简单、优雅、异常安全、无代码重复、无需自赋值测试。

If you don't know yet about exceptions, you may want to remember this idiom when learning exception safety (and ignore the throw() specifier for swap for now).如果您还不了解异常,您可能希望在学习异常安全时记住这个习惯用法(暂时忽略掉用于swapthrow()说明符)。

Writing copy-assignment operators that are safe for self-assignment is in the C++ core guidelines and for a good reason.编写对自赋值安全的复制赋值运算符是C++ 核心指南,并且有充分的理由。 Running into a self-assignment situation by accident is much easier than some of the sarcastic comments here suggest, eg when iterating over STL containers without giving it much thought:意外地陷入自我分配的情况比这里的一些讽刺评论所暗示的要容易得多,例如在迭代 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
}

Does this code make sense?这段代码有意义吗? Not really.并不真地。 Can it be easily written through carelessness or in a slightly more complex situation?是否可以通过粗心或稍微复杂的情况轻松编写? For sure.当然。 Is it wrong?这是错的吗? Not really, but even if so... Should the punishment be a segfault and a program crash?不是真的,但即使是这样......惩罚应该是段错误和程序崩溃吗? Heck no.哎呀,不。

To build robust classes that do not segfault for stupid reasons, you must test for self-assignment whenever you don't use the copy-and-swap idiom or some other safe alternative.要构建不会因为愚蠢的原因而出现段错误的健壮类,只要不使用复制和交换习惯用法或其他安全替代方法,就必须测试自分配。 One should probably opt for copy-and-swap anyway but sometimes it makes sense not to, performance wise.无论如何,一个人可能应该选择复制和交换,但有时不这样做是有道理的,性能明智。 It is also a good idea to know the self-assignment test pattern since it shows in tons of legacy code.了解自分配测试模式也是一个好主意,因为它以大量遗留代码显示。

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

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