Шаблонный вопрос оператора присваивания - PullRequest
2 голосов
/ 09 марта 2011

Я хочу убедиться, что * this! = & Rhs в операторе присваивания. Но это не скомпилируется. Есть предложения?

template <typename T>
class A {
  public:
      A() {
          std::cout << "Default Constructor" << std::endl;
      }

      A(const T& t) : m_t(t) {
          std::cout << "Templated Constructor" << std::endl;
      }

      template <typename X>
      A( const A<X>& rhs ) : m_t( (static_cast< A<T> >(rhs)).m_t ) {
            std::cout << "Copy Constructor" << std::endl;
      }

      template <typename X>
      const A& operator=( A<X>& rhs) {
            std::cout << "Assignment Operator" << std::endl;
            if (this != static_cast< A<T>* > (&rhs) )
                m_t = rhs.get();
            return *this;
      }

      T get() { return m_t; }
  private:
      T m_t;
};


class base {};
class derived : public base {};


int main()
{
    A<base*> test1;
    A<derived*> test2;
    test1 = test2;  
}

Ответы [ 5 ]

2 голосов
/ 09 марта 2011

Если это действительно беспокоит вас, у вас всегда может быть вторая не шаблонная operator=, которая не нуждается в приведении.Чтобы избежать избыточности, если это! = & Rhs, это может явно вызвать версию шаблона.Пример, иллюстрирующий, как вызывается правильный оператор:

#include <iostream>

template <class T>
struct X
{
    X& operator=(X& rhs)
    {
        std::cout << "non-template " << (this == &rhs ? "self\n" : "other\n");
    }

    template <class U>
    X& operator=(X<U>& rhs)
    {
        std::cout << "template\n";
    }
};

int main()
{
    X<int> x;
    x = x;
    X<int> y;
    x = y;
    X<double> z;
    x = z;
}
2 голосов
/ 09 марта 2011

Что вы пытаетесь сделать здесь

if (this != static_cast< A<T>* > (&rhs) )

выполняет static_cast от A<derived*> до A<base*>.

Вот что делает static_cast:

Вы можете явно конвертировать указатель типа A на указатель типа B если A является базовым классом B. Если A не базовый класс B, ошибка компилятора приведет.

A<base*> не является базовым классом A<derived*>, следовательно, ошибка.

В общем, очевидно, A<T> никогда не будет базовым классом A<X>, даже если X конвертируется в T. Так что этот бросок вне уравнения.

Решением было бы использовать reinterpret_cast<void*>(&rhs) вместо.

Обновление

Я работал над этим еще немного; вот результаты. Сначала я дам код, затем прокомментирую.

Код настройки

template <typename T>
class Aggregator {
  public:
      Aggregator() {
          std::cout << "Default Constructor" << std::endl;
      }

      Aggregator(const T& t) : m_t(t) {
          std::cout << "Constructor With Argument" << std::endl;
      }

      Aggregator& operator= (const Aggregator& rhs)
      {
          std::cout << "Assignment Operator (same type)";
          if (this->get() == rhs.get()) {
              std::cout << " -- SKIPPED assignment";
          }
          else {
              T justForTestingCompilation = rhs.get();
          }
          std::cout << std::endl;
          return *this;
      }

      template <class U>
      Aggregator& operator=(const Aggregator<U>& rhs)
      {
          std::cout << "Assignment Operator (template)";
          if (this->get() == rhs.get()) {
              std::cout << " -- SKIPPED assignment";
          }
          else {
              T justForTestingCompilation = rhs.get();
          }
          std::cout << std::endl;
          return *this;
      }

      T get() const { return m_t; }
  private:
      T m_t;
};


class base {};
class derived : public base {};
class unrelated {};

// This is just for the code to compile; in practice will always return false
bool operator==(const base& lhs, const base& rhs) { return &lhs == &rhs; }

Важные моменты о том, что происходит до сих пор:

  1. У меня нет конструктора копирования. В реальной жизни мы переместили логику присваивания в конструктор копирования и реализовали оператор присваивания с помощью copy и swap . Сохраняя код коротким (er) на данный момент.
  2. Операторы присваивания на самом деле ничего не делают, но они скомпилируют iff их «нормальная» версия «делай, что хочешь».
  3. Есть два оператора доставки; первый для назначения Aggregate<T> на Aggregate<T> и второй для присвоения Aggregate<T1> на Aggregate<T2>. Это прямо из ответа Тони, и это «правильный путь».
  4. Свободный operator== предназначен для подделки оператора сравнения для типов, в которых он не определен неявно. В частности, это необходимо для кода, который содержит Aggregate<U> для компиляции, когда U не является примитивным типом.

Код упражнения

Вот где все самое интересное:

int main(int argc, char* argv[])
{
    base b;
    derived d;
    unrelated u;

    Aggregator<base*> aggPB(&b);
    Aggregator<base*> aggPBDerivedInstance(&d);
    Aggregator<derived*> aggPD(&d);
    Aggregator<unrelated*> aggPU(&u);

    Aggregator<base> aggB(b);
    Aggregator<base> aggBDerivedInstance(d); // slicing occurs here
    Aggregator<derived> aggD(d);
    Aggregator<unrelated> aggU(u);

    std::cout << "1:" << std::endl;

    // base* = base*; should compile, but SKIP assignment
    // Reason: aggregate values are the same pointer
    aggPB = aggPB;

    // base = base; should compile, perform assignment
    // Reason: aggregate values are different copies of same object
    aggB = aggB;

    std::cout << "2:" << std::endl;

    // base* = base*; should compile, perform assignment
    // Reason: aggregate values are pointers to different objects
    aggPB = aggPBDerivedInstance;

    // base = base; should compile, perform assignment
    // Reason: aggregate values are (copies of) different objects
    aggB = aggBDerivedInstance;

    std::cout << "3:" << std::endl;

    // base* = derived*; should compile, perform assignment
    // Reason: aggregate values are pointers to different objects
    aggPB = aggPD;

    // base = derived; should compile, perform assignment (SLICING!)
    // Reason: derived is implicitly convertible to base, aggregates are (copies of) different objects
    aggB = aggD;

    std::cout << "4:" << std::endl;

    // base* = derived*; should compile, but SKIP assignment
    // Reason: aggregate values are (differently typed) pointers to same object
    aggPBDerivedInstance = aggPD;

    // base = derived; should compile, perform assignment (SLICING!)
    // Reason: derived is implicitly convertible to base, aggregates are (copies of) different objects
    aggBDerivedInstance = aggD;

    std::cout << "5:" << std::endl;

    // derived* = base*; should NOT compile
    // Reason: base* not implicitly convertible to derived*
    // aggPD = aggPB;

    // derived = base; should NOT compile
    // Reason: base not implicitly convertible to derived
    // aggD = aggB;

    return 0;
}

Будет выведено:

Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
Constructor With Argument
1:
Assignment Operator (same type) -- SKIPPED assignment
Assignment Operator (same type)
2:
Assignment Operator (same type)
Assignment Operator (same type)
3:
Assignment Operator (template)
Assignment Operator (template)
4:
Assignment Operator (template) -- SKIPPED assignment
Assignment Operator (template)
5:

Итак ... что мы узнаем из этого?

  1. Оператор шаблонного присваивания, как написано, не будет компилироваться, если не существует неявного преобразования между агрегированными типами. Это хорошая вещь. Этот код не будет компилироваться, а не падать на вас.
  2. Проверка на равенство спасла нам только два назначения, и оба они являются указателями (которые настолько дешевы, что их не нужно проверять).

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

Однако, если:

  1. T1 и T2 одного типа или существует неявное преобразование, и
  2. T1 или оператор преобразования T2 стоят дорого (это сразу убирает примитивы из картинки), и
  3. A bool operator== (const T1& lhs, const T2& rhs), у которого затраты времени выполнения намного меньше, чем у вышеперечисленных операторов присвоения / преобразования

затем проверка на равенство может имеет смысл (зависит от того, как часто вы ожидаете, что operator== вернет true).

Заключение

Если вы намереваетесь использовать Aggregator<T> только с указателями (или любым другим примитивом), тогда тесты на равенство излишни.

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

1 голос
/ 09 марта 2011

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

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

poor_assignment& operator=(const poor_assignment& rhv)
{
    this->Release(); // == rhv.Release() in case of self-assignment
    this->Create(rhv.GetResources()); 
}
0 голосов
/ 09 марта 2011

Лично я считаю, что следующее - самое элегантное решение. Я не уверен, почему я не получил это во-первых - я первоначально использовал компилятор кровопролитного c ++, и он, казалось, потерпел неудачу - но g ++ это самый чистый?

Если люди со мной не согласятся, я уберу свой ответ и передам его кому-нибудь еще. Обратите внимание, что A * на самом деле означает A *, но примечание требуется

  template <typename X>
  const A& operator=( A<X>& rhs) {
        std::cout << "Assignment Operator" << std::endl;
        if (this != reinterpret_cast< A* >(&rhs))  
            m_t = rhs.get();               
        return *this;
  }
0 голосов
/ 09 марта 2011

Хорошая версия: Реализуйте оператор присваивания, который принимает тот же тип, что и сам класс:

const A& operator=( A<T>& rhs) {
    std::cout << "copy assignment operator" << std::endl;
    if(this != &rhs)
        m_t = rhs.m_t;
    return *this;
}

'Грязная' версия: приведите адрес каждого объекта к intptr_t исравните простые значения:

template<class X>
const A& operator=( A<X>& rhs) {
    std::cout << "Assignment Operator" << std::endl;
    if((intptr_t)(this) != (intptr_t)(&rhs))
        m_t = rhs.get();
    return *this;
}

Редактировать: На самом деле эта вторая версия не будет работать, поскольку вместо нее используется оператор присваивания, созданный компилятором.Так что просто внедрите один самостоятельно. - конец редактирования
Кроме того, вам не нужно использовать rhs.get(), просто используйте rhs.m_t, поскольку вы можете получить доступ к закрытым членам из самого класса.

...