C ++ операторы перегрузки, присваивания, глубокого копирования и добавления - PullRequest
4 голосов
/ 07 июня 2009

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

Я создал простой класс счетчика, который имеет (на данном этапе) один член, val (int).

Я инициализировал три из этих счетчиков, varOne to varThree, и хочу, чтобы третий счетчик был суммой первых двух (например, varThree.val установлен в 5 в приведенном ниже коде)

counter::counter(int initialVal)
{
    val = initialVal;
    //pVal = new int;
    //*pVal = 10; // an arbitrary number for now
}

int main (int argc, char const* argv[])
{
    counter varOne(3), varTwo(2), varThree;
    varThree = varOne + varTwo;

    return 0;
}

Я перегружен оператор + вот так:

counter operator+(counter& lhs, counter& rhs)
{
    counter temp(lhs.val + rhs.val);
    return temp;
}

Я сделал эту функцию не-членом и другом класса счетчика, чтобы он мог получить доступ к закрытым значениям.

Моя проблема начинается при добавлении другого закрытого члена, pVal (указатель на int). Добавление этого означает, что я больше не могу делать простое varThree = varOne копирование, потому что, когда varOne уничтожен, varThree.pVal все равно будет указывать на тот же бит памяти.

Я перегружен operator= следующим образом.

int counter::getN()
{
    return *newVal;
}

counter& counter::operator=(counter &rhs)
{
    if (this == &rhs) return *this;
    val = rhs.val;
    delete pVal;
    pVal = new int;
    *pVal = rhs.getN();
    return *this;
}

Теперь, если я делаю что-то вроде varThree = varOne, все копируется правильно, однако при попытке сделать varThree = varOne + varTwo выдается следующая ошибка:

counter.cpp: In function ‘int main(int, const char**)’:
counter.cpp:96: error: no match for ‘operator=’ in ‘varThree = operator+(counter&, counter&)(((counter&)(& varTwo)))’
counter.cpp:55: note: candidates are: counter& counter::operator=(counter&)
make: *** [counter] Error 1

Похоже, что counter::operator= не может справиться с возвращаемым выводом из operator+, и мне нужно перегрузить operator= дальше, чтобы принять тип, возвращаемый operator+, но у меня не было удачи, и я начинаю думать, что, возможно, я сделал что-то в корне неправильно.

Ответы [ 3 ]

11 голосов
/ 07 июня 2009

Вы должны передать свои параметры как константную ссылку. Например:

counter& counter::operator=( const counter &rhs )

И аналогично для оператора + (). Это необходимо для того, чтобы иметь возможность привязать временные значения к параметру (ам) функции. Временные значения создаются при возврате по значению, поэтому, когда вы говорите:

varOne + varTwo

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

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

1 голос
/ 07 июня 2009

Ключевой момент здесь (о чем говорил предыдущий автор), но стоит подчеркнуть, что выражения в C ++ могут быть классифицированы как rvalues ​​или lvalues.

В этих категориях много подробностей, но полезная эвристика для руководства вашей интуицией такова: если вы можете взять адрес выражения (такого как переменная), это lvalue (здесь есть гораздо больше истории, но это хорошее место для начала).

Если это действительно не lvalue, это rvalue - и полезной эвристикой для rvalue является то, что они представляют «скрытые» временные объекты, которые компилятор создает для работы вашего кода. Эти объекты создаются и уничтожаются компилятором за кулисами.

Почему это актуально здесь?

Хорошо, в C ++ 98/03 (который, как я предполагаю, вы используете), запомните следующие два правила:

1) Только lvalue-выражения могут связываться с неконстантными ссылками (игнорируя приведения) 2) выражения rvalue могут связываться только с константными ссылками (игнорируя приведение)

Пример поможет здесь:

// Consider the function foo below - it returns an int - 
// whenever this function is called, the compiler has
// to behave as if a temporary int object with the value 5 is returned.
// The use of 'foo()' is an expression that is an rvalue - try typing &foo() -
// [Note: if foo was declared as int& foo(), the story gets complicated, so
//   i'll leave that for another post if someone asks]

int foo() { return 5; }

void bind_r(int& r) { return; }
void bind_cr(const int& cr) { return; }

int main()
{
   int i = 10;  // ok
   int& ri = i; // ok binding lvalue to non-const reference, see rule #1
   int& ri2 = foo(); // Not ok, binding a temporary (rvalue) to a non-const reference 
        // The temporary int is created & destroyed by compiler here

   const int& cri = foo(); // ok - see rule #2, temporary int is NOT destroyed here

  //Similarly
   bind_r(i); // ok - rule #1
   bind_r(foo()); // NOT ok - rule #2
   bind_cr(foo()); // ok - rule #2


  // Since the rules above keep you out of trouble, but do not exhaust all possibilities
  // know that the following is well-formed too:
  const int& cri2 = i;
  bind_cr(i);
  bind_cr(cri);
  bind_cr(cri2); 

}

Когда вы привязываете rvalue к константной ссылке, вы в основном продлеваете время жизни временного объекта до времени жизни (в данном случае области видимости) ссылки (и компилятор не может просто уничтожить его в конце этой ссылки). выражение) - так вы получите ссылку на действительный объект.

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

p.s. Существуют некоторые другие проблемы с вашим кодом (например, почему вы уничтожаете и создаете память, на которую ваш объект указывает только при каждом назначении, и отсутствие конструктора и деструктора копирования), и если никто не обращался к ним к тому времени этот пост появляется, я буду :) 1020 *

p.s. Может также стоить знать, что C ++ 0x добавляет что-то, известное как ссылки на rvalue (неконстантные), которые преимущественно связываются с rvalue и предоставляют чрезвычайно мощные возможности оптимизации для программиста (без необходимости полагаться на возможности оптимизации компилятора) - они также помогают решить проблему создания идеальной функции пересылки в C ++ - но теперь мы отвлеклись;)

1 голос
/ 07 июня 2009

Другой способ решения этой проблемы - использовать шаблон PImpl и обмен для оператора присваивания. Предполагая, что у вас все еще есть конструктор counter (int), вы можете написать operator = следующим образом

counter& counter::operator=(const counter& rhs) {
  counter temp(rhs.getN());
  std::swap(&pVal,rhs.pVal);
  return *this;
}

Это дает преимущество, оставляя грязные функции управления памятью в конструкторе и деструкторе там, где они должны быть.

...