Почему в минимально необходимом фрагменте кода в задании перемещения закомментированная строка *arg = nullptr;
незаконна и arg.p = nullptr;
в порядке? Если я правильно понимаю, оба изменяют rvalue, но при компиляции *arg = nullptr;
выдает ошибку:
левый операнд должен быть l-значением
Я ожидал, что *arg
предоставит доступ к arg
p
из-за перегрузки операторов.
#include <iostream>
using namespace std;
class Ptr {
public:
Ptr(double arg) :p{ new double(arg) } {}
// copy constructor
Ptr(const Ptr& arg) {
p = new double(*(*arg));
}
// move assignment
Ptr& operator=(Ptr&& arg) {
delete p;
p = *arg;
cout << "move type of *arg " << typeid(*arg).name() << endl;
arg.p = nullptr;
//*arg = nullptr; // illegal
return *this;
}
double* operator*() {
return p;
}
const double* operator*() const {
return p;
}
~Ptr() {
delete p;
}
private:
double* p;
};
Ptr copy(Ptr a) { return a; }
int main() {
Ptr pt{ 2 };
Ptr pt2{ 3 };
pt2 = copy(pt);
return 0;
}
Попробовал обратиться за помощью к
Обновлено:
Удален вводящий в заблуждение комментарий к operator*
. Я хотел бы сохранить код как есть. Проблема частично связана с PPP3, глава 17, упражнением Бьярна Страуструпа. Меня интересует ПОЧЕМУ.
«Попробовал помощь в ChatGpt...» ИИ не является хорошим/надежным источником проблем с кодированием, особенно для такого языка, как C++.
Возврат вашего перегруженного operator*
по значению означает, что вызов этих функций является выражением rvalue.
«Оба изменяют rvalue». Нет, arg.p
— это выражение lvalue, даже arg
само по себе является выражением lvalue, хотя его объявленный тип — «ссылка на rvalue». Это заблуждение неоднократно рассматривалось в SO.
«Если я правильно понимаю, оба изменяют rvalue». Имя «rvalue» происходит от «правой стороны значения присвоения». Вы спрашиваете о левой части задания (которое привело к названию lvalue)...
@JaMiT Я интерпретирую это как «оба изменяют rvalue, поэтому не может быть так, чтобы работал только один из них».
operator*()
возвращает копию адреса памяти p
, который представляет собой шестнадцатеричное целое число. Чтобы вернуть это по ссылке, верните double*&
.
@WeijunZhou «Я интерпретирую это как [...]» — вы можете интерпретировать это так, если хотите. Это не имеет никакого отношения к моей точке зрения о том, что «изменение значения r (путем присвоения)» должно немедленно вызвать подозрение, что заявленное понимание может быть неверным.
Теперь я понимаю, что комментарий к operator*
неверен, однако я все равно хотел бы оставить код как есть. Проблема частично связана с главой 17 PPP3, упражнение Бьярна Страуструпа.
@BrunoMraz «Меня интересует ПОЧЕМУ...» Я объяснил почему в своем ответе ниже. В настоящее время *arg
является выражением rvalue. Если вы измените тип возвращаемого значения на ссылку lvalue, он станет выражением lvalue.
Я ожидаю, что *arg предоставит доступ к arg p из-за перегрузки операторов.
Проблема в том, что ваш перегруженный возврат operator*
по значению, что означает вызов этих функций, как в *arg
, является выражением rvalue.
Чтобы решить эту проблему, вы можете вернуть указатель по ссылке lvalue, как показано ниже.
//---------v------------------>return by reference
double*& operator*() {
return p;
}
Почему отрицательные голоса? В ответе нет ничего плохого. Кнопка «против» предназначена для бесполезного ответа, хотя этот ответ очень полезен.
Я хотел бы уточнить, правильно ли я понимаю, что я могу интерпретировать operator*
как вызов функции, подобный этому Ptr::operator*(pt)
, и поскольку pt
извлекло свое rvalue при назначении перемещения из-за Ptr&& arg
, operator*
продолжает возвращать rvalue, которое является данными, а не переменной. С другой стороны, arg.p
осуществляет доступ к члену, где arg
— это rvalue, а все, что следует за dot
, — это lvalue, потому что именно так ведет себя C++ как язык. Я на правильном пути с этим объяснением? Если да, то я мог бы сказать, что в данном случае это просто вопрос синтаксиса C++.
@BrunoMraz Это намного проще. Например, всякий раз, когда у нас есть функция, которая возвращает значение, например, int func();
, тогда вызов этой функции, например func()
, является rvalue. Теперь скажем, что если у нас есть другая функция, такая как int& bar();
, то вызов этой второй функции, такой как bar()
, является выражением lvalue. Они взяты из определений lvalue, rvalue и т. д. Аналогично, для оператора точки .
существуют разные правила для операндов и его результата. В вашем примере arg.p
— это выражение lvalue.
Теперь я понял, спасибо за объяснение. Переход от double*
к double*&
в operator*
теперь имеет для меня смысл.
@BrunoMraz Пожалуйста :)
позволяет читать и писать *p Нет. Вы хотели
double*& operator*()
.