#c #return #copy-constructor #assignment-operator
#c #Возврат #копировать-конструктор #оператор присваивания
Вопрос:
Я запускаю этот код для экспериментов с конструктором копирования и оператором присваивания
class AClass {
private:
int a;
public:
AClass (int a_) : a(a_) {
cout << " constructor AClass(int) " << a << endl;
}
AClass(const AClass amp; x) : a(x.a) {
cout << " copy constructor AClass(const AClass amp;) " << a << endl;
}
AClass amp; operator=(const AClass amp; x) {
a = x.a;
cout << " AClassamp; operator=(const AClass amp;) " << a - endl;
return *this;
}
};
AClass g () {
AClass x(8);
return x;
}
int main () {
cout << " before AClass b = g() " << endl;
AClass b = g();
cout << " after" << endl;
cout << " before AClass c(g()) " << endl;
AClass c (g());
cout << " after" << endl;
}
и обнаружил, что не отображается сообщение для return x;
Почему?
Не следует ли вызывать конструктор копирования или operator=?
Это результат:
перед классом доступа b = g() класс конструктора (int) 8 после перед классом c(g()) класс конструктора (int) 8 после
Комментарии:
1. Вместо того, чтобы использовать
<pre><code>
в вашем вопросе, пожалуйста, выберите пример кода и нажмите{}
кнопку в редакторе. Это позволяет лучше сохранять исходный код.2. Поскольку вы экспериментируете, может быть полезно знать, что вы можете принудительно скопировать, если вы создадите
x
неавтоматическую переменную вg()
, вот так:AClass *x = new AClass(8); return (*x);
. Конечно, написание подобного кода позволит вам получить звание капитана S.S. MemoryLeak.
Ответ №1:
Компилятору разрешено исключить копирование в подобном случае. Это называется оптимизацией возвращаемого значения.
Комментарии:
1. Спасибо, но если RVO не выполнен, что выполняется: скопировать конструктор или operator=? Я сделал private operator=, и оба вызова все еще компилируются, поэтому напрашивается вывод, что будет использоваться конструктор копирования. Верно? Еще раз спасибо
2. @ciber: как правило, да, конструктор копирования используется для передачи параметров в функции и из них. Я не верю, что оператор присваивания может быть исключен таким образом, но не цитируйте меня по этому поводу.
3. @Dennis Нет, это не так. Операция присваивания никогда не вызывается компилятором, только явно программистом.
Ответ №2:
В C компилятору разрешено удалять вызовы конструктора копирования практически при любых обстоятельствах, даже если конструктор копирования имеет побочные эффекты, такие как распечатка сообщения. Как следствие, также разрешено вставлять вызовы конструктора копирования практически в любой момент, когда ему заблагорассудится. Это немного затрудняет написание программ для проверки вашего понимания копирования и присваивания, но означает, что компилятор может агрессивно удалять ненужное копирование в реальном коде.
Ответ №3:
Это известно как «оптимизация возвращаемого значения». Если объект возвращается по значению, компилятору разрешается сконструировать его в местоположении, доступном вызывающей стороне после возврата функции; в этом случае конструктор копирования вызываться не будет.
Также разрешено обрабатывать его как обычную автоматическую переменную и копировать его при возврате, поэтому конструктор копирования должен быть доступен. Вызывается он или нет, зависит от компилятора и настроек оптимизации, поэтому вам не следует полагаться ни на то, ни на другое поведение.
Ответ №4:
Это называется копированием. Компилятору разрешено удалять копии практически в любой ситуации. Наиболее распространенным случаем являются RVO и NRVO, что в основном приводит к построению возвращаемых значений на месте. Я продемонстрирую преобразование.
void g (char* memory) {
new (memory) AClass(8);
}
int main () {
char __hidden__variable[sizeof(AClass)];
g(__hidden__variable);
AClassamp; b = *(AClass*)amp;__hidden__variable[0];
cout -- " after" -- endl;
// The same process occurs for c.
}
Код имеет тот же эффект, но теперь существует только один экземпляр AClass.
Ответ №5:
Возможно, компилятор оптимизировал вызов конструктора копирования. По сути, он перемещает объект.
Комментарии:
1. Учитывая изменения, внесенные в C 0x, я бы не решился на такое упрощение.
2. C 0x просто позволил четко указать движущиеся объекты. И даже тогда компилятор может полностью игнорировать вас и не вызывать ваш конструктор перемещения (что-то вроде того, что сбило меня с толку).
Ответ №6:
Если вы хотите увидеть, какой конструктор вызвал бы компилятор, вы должны победить RVO. Замените свою g()
функцию таким образом:
int i;
AClass g () {
if(i) {
AClass x(8);
return x;
} else {
AClass x(9);
return x;
}
}
Комментарии:
1. Я тоже не уверен, что это сработает. Компилятору не требуется создавать обе возможные версии
x
в отдельных блоках памяти. Если бы у вас были две разные переменные в одной и той же области видимости, и вы условно возвращали бы любую из них, вам, вероятно, повезло бы больше.2. На самом деле, это приводит к заметно отличающимся результатам в GCC без оптимизации.