Проверить "самостоятельное назначение" в конструкторе копирования?

Сегодня в университете мне рекомендовали профессором, что я проверил бы (this != &copy) в конструкторе копирования, аналогично тому, как вы должны это делать при перегрузке operator=. Однако я поставил под сомнение это, потому что я не могу представить ни одной ситуации, когда this когда-либо был бы равен аргументу при построении объекта.

Он признал, что я сделал хороший момент. Итак, мой вопрос заключается в том, имеет ли смысл выполнять эту проверку, или это невозможно, чтобы это испортило?

Изменить: Я думаю, я был прав, но я просто оставлю это открытым некоторое время. Может быть, кто-то придумает какую-нибудь сумасшедшую загадочную магию С++.

Edit2: Test a(a) компилируется на MinGW, но не MSVS10. Test a = a компилируется на обоих, поэтому я предполагаю, что gcc будет вести себя как-то похоже. К сожалению, VS не показывает отладочное сообщение с "Variable a used without initialized". Тем не менее, это сообщение действительно отображается для int i = i. Может ли это считаться недостатком языка С++?

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

Ответы

Ответ 1

Лично я считаю, что ваш профессор ошибается, и вот почему.

Конечно, код будет компилироваться. И конечно, код сломан. Но что касается вашего профессора с его рассуждениями, а затем он приходит к выводу: "О, хорошо, мы должны убедиться, что мы самонадеянны, а если да, то просто вернемся".

Но это плохо, по той же причине, почему глобальный catch-all catch(...), который ничего не делает, - это Evil. Вы предотвращаете непосредственную проблему, но проблема все еще существует. Недопустимый код. Вы не должны вызывать конструктор с указателем на себя. Решение не должно игнорировать проблему и продолжать ее. Решение состоит в том, чтобы исправить код. Лучшее, что может произойти, - это немедленно свернуть ваш код. Хуже всего то, что код будет продолжаться в недопустимом состоянии в течение некоторого периода времени, а затем либо сбой позже (когда стек вызовов не принесет вам пользы), либо создаст недопустимый вывод.

Нет, твой профессор ошибается. Выполняйте задание без проверки на самоопределение. Найдите дефект в обзоре кода или сообщите об ошибке кода и найдите его в сеансе отладки. Но не продолжайте так, как будто ничего не произошло.

Ответ 2

Это действительный С++ и вызывает конструктор копирования:

Test a = a;

Но это не имеет смысла, потому что a используется до его инициализации.

Ответ 3

Ваш инструктор, вероятно, пытается избежать этой ситутации -

#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

Если вы хотите быть параноиком, то:

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

Вы не хотите продолжать, если this == &copy. Я никогда не беспокоился об этой проверке. В коде, с которым я работаю, часто возникает ошибка. Однако, если ваш опыт отличается от того, что утверждение может стоить того.

Ответ 5

Ваш инструктор может подумать о проверке самозадачи в операторе присваивания копии.

Рекомендуется проверить самоопределение в операторе присваивания как в Sutter, так и Alexandrescu "Стандарты кодирования С++", а Скотт Майер ранее "Эффективный С++".

Ответ 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. Простой, изящный, безопасный, без дублирования кода, и нет необходимости проводить самоопределение.

Если вы еще не знаете об исключениях, вы можете запомнить эту идиому при обучении безопасности исключений (и игнорировать спецификатор throw() для swap).

Ответ 8

Я согласен, что самопроверка не имеет никакого смысла в конструкторе копирования, поскольку объект еще не создан, но ваш профессор прав насчет добавления проверки, чтобы избежать дальнейших проблем. Я пробовал с помощью/без самопроверки и получил неожиданный результат, когда не проверяется самоконтроль и ошибка времени выполнения, если существует самоконтроль.

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

    ~Test()
    {....}

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

 }

При создании конструктора копирования и вызываемой функции-члена я didn

 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

Как правило, оператор = и конструктор копирования вызывает функцию копирования, поэтому возможно самозаряжение. Итак,

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, вызывается перегрузка оператора, которая вызывает функцию копирования, которая затем обнаруживает самоопределение.