Подтвердить что ты не робот

Оператор виртуального присваивания С++

Оператор присваивания в С++ может быть сделан виртуальным. Почему это требуется? Можем ли мы сделать и другие операторы виртуальными?

4b9b3361

Ответ 1

Оператор присваивания не требуется делать виртуальным.

Ниже приведено обсуждение operator=, но оно также относится к любой перегрузке оператора, которая принимает рассматриваемый тип, и к любой функции, которая принимает этот тип.

В приведенном ниже обсуждении показано, что ключевое слово virtual не знает о наследовании параметров в отношении поиска сигнатуры соответствия. В последнем примере показано, как правильно обрабатывать присваивание при работе с унаследованными типами.


Виртуальные функции не знают о наследовании параметров:

Функциональная подпись должна быть одинаковой для виртуальной игры. Так что даже знаю в следующем примере, оператор = сделан виртуальным. Вызов никогда не будет действовать как виртуальная функция в D, потому что параметры и возвращаемое значение оператора = отличаются.

Функция B::operator=(const B& right) и D::operator=(const D& right) на 100% полностью различна и рассматривается как две различные функции.

class B
{
public:
  virtual B& operator=(const B& right)
  {
    x = right.x;
    return *this;
  }

  int x;

};

class D : public B
{
public:
  virtual D& operator=(const D& right)
  {
    x = right.x;
    y = right.y;
    return *this;
  }
  int y;
};

Значения по умолчанию и имеющие 2 перегруженных оператора:

Вы можете определить виртуальную функцию, которая позволит вам установить значения по умолчанию для D, если она назначена переменной типа B. Это даже если ваша переменная B действительно является D, хранящейся в ссылке B. Вы будете не получить функцию D::operator=(const D& right).

В следующем случае используется назначение из двух объектов D, хранящихся внутри ссылок 2 B... используется переопределение D::operator=(const B& right).

//Use same B as above

class D : public B
{
public:
  virtual D& operator=(const D& right)
  {
    x = right.x;
    y = right.y;
    return *this;
  }


  virtual B& operator=(const B& right)
  {
    x = right.x;
    y = 13;//Default value
    return *this;
  }

  int y;
};


int main(int argc, char **argv) 
{
  D d1;
  B &b1 = d1;
  d1.x = 99;
  d1.y = 100;
  printf("d1.x d1.y %i %i\n", d1.x, d1.y);

  D d2;
  B &b2 = d2;
  b2 = b1;
  printf("d2.x d2.y %i %i\n", d2.x, d2.y);
  return 0;
}

Печать

d1.x d1.y 99 100
d2.x d2.y 99 13

Что показывает, что D::operator=(const D& right) никогда не используется.

Без ключевого слова virtual в B::operator=(const B& right) у вас будут те же результаты, что и выше, но значение y не будет инициализировано. То есть он использовал бы B::operator=(const B& right)


Последний шаг, чтобы связать все это вместе, RTTI:

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

virtual B& operator=(const B& right)
{
  const D *pD = dynamic_cast<const D*>(&right);
  if(pD)
  {
    x = pD->x;
    y = pD->y;
  }
  else
  {
    x = right.x;
    y = 13;//default value
  }

  return *this;
}

Ответ 2

Это зависит от оператора.

Точка создания оператора присваивания virtual - это позволить вам извлечь выгоду из возможности переопределить его для копирования большего количества полей.

Итак, если у вас есть Base & и у вас есть Derived & как динамический тип, а в Derived - больше полей, скопированы правильные вещи.

Однако существует риск того, что ваш LHS является производным, а RHS - базой, поэтому, когда виртуальный оператор работает в Derived, ваш параметр не является производным, и у вас нет способа получить поля из него.

Вот хорошее обсуждение: http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html

Ответ 3

Брайан Р. Бонди написал:


Последний шаг, чтобы связать все это вместе, RTTI:

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

virtual B& operator=(const B& right)
{
  const D *pD = dynamic_cast<const D*>(&right);
  if(pD)
  {
    x = pD->x;
    y = pD->y;
  }
  else
  {
    x = right.x;
    y = 13;//default value
  }

  return *this;
}

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

Компилятор генерирует оператор присваивания, который принимает аргумент const D &, который не является виртуальным и не делает того, что вы можете себе представить.

Вторая проблема - это тип возвращаемого значения, вы возвращаете базовую ссылку на производный экземпляр. Вероятно, это не проблема, поскольку код работает в любом случае. Тем не менее, лучше возвращать ссылки соответственно.

Третий выпуск, оператор присваивания производного типа не вызывает оператор присваивания базового класса (что, если есть частные поля, которые вы хотели бы скопировать?), объявление оператора присваивания как виртуального не заставит компилятор сгенерировать его для вас. Это скорее побочный эффект отсутствия не менее двух перегрузок оператора присваивания для получения желаемого результата.

Учитывая базовый класс (тот же, что и в столбце, который я цитировал):

class B
{
public:
    virtual B& operator=(const B& right)
    {
        x = right.x;
        return *this;
    }

    int x;
};

Следующий код завершает решение RTTI, которое я цитировал:

class D : public B{
public:
    // The virtual keyword is optional here because this
    // method has already been declared virtual in B class
    /* virtual */ const D& operator =(const B& b){
        // Copy fields for base class
        B::operator =(b);
        try{
            const D& d = dynamic_cast<const D&>(b);
            // Copy D fields
            y = d.y;
        }
        catch (std::bad_cast){
            // Set default values or do nothing
        }
        return *this;
    }

    // Overload the assignment operator
    // It is required to have the virtual keyword because
    // you are defining a new method. Even if other methods
    // with the same name are declared virtual it doesn't
    // make this one virtual.
    virtual const D& operator =(const D& d){
        // Copy fields from B
        B::operator =(d);
        // Copy D fields
        y = d.y;
        return *this;
    }

    int y;
};

Это может показаться полным решением, это не так. Это не полное решение, потому что, когда вы выходите из D, вам понадобится 1 operator =, который принимает const B &, 1 operator =, который принимает const D & и один оператор, который принимает const D2 &. Вывод очевиден, число перегрузок operator =() эквивалентно числу суперклассов + 1.

Учитывая, что D2 наследует D, рассмотрим, как выглядят два унаследованных метода operator =().

class D2 : public D{
    /* virtual */ const D2& operator =(const B& b){
        D::operator =(b); // Maybe it a D instance referenced by a B reference.
        try{
            const D2& d2 = dynamic_cast<const D2&>(b);
            // Copy D2 stuff
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }

    /* virtual */ const D2& operator =(const D& d){
        D::operator =(d);
        try{
            const D2& d2 = dynamic_cast<const D2&>(d);
            // Copy D2 stuff
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }
};

Очевидно, что оператор = (const D2 &) просто копирует поля, представьте, как будто он был там. Мы можем заметить шаблон в унаследованных перегрузках operator =(). К сожалению, мы не можем определить методы виртуальных шаблонов, которые позаботятся об этом шаблоне, нам нужно скопировать и вставить несколько раз один и тот же код, чтобы получить полный полиморфный оператор присваивания, единственное решение, которое я вижу. Также применяется к другим бинарным операторам.


Изменить

Как упоминалось в комментариях, самое меньшее, что можно сделать, чтобы облегчить жизнь, - это определить оператор присваивания верхнего класса суперкласса =() и вызвать его из всех других методов superclass operator =(). Также при копировании полей может быть определен метод _copy.

class B{
public:
    // _copy() not required for base class
    virtual const B& operator =(const B& b){
        x = b.x;
        return *this;
    }

    int x;
};

// Copy method usage
class D1 : public B{
private:
    void _copy(const D1& d1){
        y = d1.y;
    }

public:
    /* virtual */ const D1& operator =(const B& b){
        B::operator =(b);
        try{
            _copy(dynamic_cast<const D1&>(b));
        }
        catch (std::bad_cast){
            // Set defaults or do nothing.
        }
        return *this;
    }

    virtual const D1& operator =(const D1& d1){
        B::operator =(d1);
        _copy(d1);
        return *this;
    }

    int y;
};

class D2 : public D1{
private:
    void _copy(const D2& d2){
        z = d2.z;
    }

public:
    // Top-most superclass operator = definition
    /* virtual */ const D2& operator =(const B& b){
        D1::operator =(b);
        try{
            _copy(dynamic_cast<const D2&>(b));
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }

    // Same body for other superclass arguments
    /* virtual */ const D2& operator =(const D1& d1){
        // Conversion to superclass reference
        // should not throw exception.
        // Call base operator() overload.
        return D2::operator =(dynamic_cast<const B&>(d1));
    }

    // The current class operator =()
    virtual const D2& operator =(const D2& d2){
        D1::operator =(d2);
        _copy(d2);
        return *this;
    }

    int z;
};

Нет необходимости в методе заданных значений по умолчанию, потому что он получит только один вызов (при перегрузке базового оператора =()). Изменения при копировании полей выполняются в одном месте, и все перегрузки operator =() затрагиваются и несут их предполагаемую цель.

Благодарим sehe за предложение.

Ответ 4

виртуальное назначение используется в следующих сценариях:

//code snippet
Class Base;
Class Child :public Base;

Child obj1 , obj2;
Base *ptr1 , *ptr2;

ptr1= &obj1;
ptr2= &obj2 ;

//Virtual Function prototypes:
Base& operator=(const Base& obj);
Child& operator=(const Child& obj);

case 1: obj1 = obj2;

В этой виртуальной концепции не играет никакой роли, поскольку мы называем operator= классом Child.

case 2 & 3: * ptr1 = obj2;
                * ptr1 = * ptr2;

Здесь назначение будет не таким, как ожидалось. Причина operator= вызывается вместо класса Base.

Его можно исправить с помощью: 1) Литье

dynamic_cast<Child&>(*ptr1) = obj2;   // *(dynamic_cast<Child*>(ptr1))=obj2;`
dynamic_cast<Child&>(*ptr1) = dynamic_cast<Child&>(*ptr2)`

2) Виртуальная концепция

Теперь просто используя virtual Base& operator=(const Base& obj) не поможет, поскольку разные подписи в Child и Base для operator=.

Нам нужно добавить Base& operator=(const Base& obj) в класс Child вместе с его обычным определением Child& operator=(const Child& obj). Его важно включить позднее определение, так как при отсутствии этого оператора присваивания по умолчанию будет вызываться. (obj1=obj2 может не дать желаемого результата)

Base& operator=(const Base& obj)
{
    return operator=(dynamic_cast<Child&>(const_cast<Base&>(obj)));
}

случай 4: obj1 = * ptr2;

В этом случае компилятор ищет определение operator=(Base& obj) в Child, поскольку operator= вызывается в Child. Но поскольку его нет и Base не могут быть продвинуты до Child неявно, он будет через ошибку. (Требуется листинг, как obj1=dynamic_cast<Child&>(*ptr1);)

Если мы реализуем в соответствии с case2 & 3, этот сценарий будет рассмотрен.

Как видно, виртуальное присваивание делает вызов более элегантным при назначении с помощью указателей/ссылок базового класса.

Можно ли сделать и других операторов виртуальными? Да

Ответ 5

Требуется только тогда, когда вы хотите гарантировать, что классы, полученные из вашего класса, будут правильно скопировать все их члены. Если вы ничего не делаете с полиморфизмом, вам действительно не нужно беспокоиться об этом.

Я ничего не знаю, что помешало бы вам виртуализировать любой оператор, который вы хотите - они не что иное, как вызовы специальных случаев.

Эта страница дает отличное и подробное описание того, как все это работает.

Ответ 6

Оператор - это метод со специальным синтаксисом. Вы можете рассматривать его как любой другой метод...