Возможно, вы задавались следующими вопросами:
Порядок передачи переменной в функцию?
Как передаётся переменная в функцию?
Создаётся ли временная переменная, при вызове функции?
Данный пример демонстрирует ответ на ваши вопросы.
При вызове функции с передачей объекта по значению, происходит копирование объекта во временную переменную того же типа. При этом вызывается конструктор копирования. Если конструктор копий не найден в этом классе, то выполняется побитовое копирование объекта во временную переменную.
Пример ниже демонстрирует это:
Обратите внимание на то, что отсутсвтие конструктора копий в классе не приводит к вызову определённого в этом классе оператора приваивания (operator =). Чтобы в этом убедиться, закомментируйте конструктор копий:
И теперь вывод программы будет следующим:
Допустим, вы написали класс, который использует указатели, выделяет под них динамическую память, а потом корректно её освобождает по удалению объекта. Всё работает правильно, но вы не реализовали конструктор копирования.
В этом случае вы рискуете встретиться с весьма неявной ошибкой, когда попытаетесь передать объект своего класса в функцию по значению.
Дело в том, что при отсутствии конструктора копий, как было сказано выше, выполняется стандартная операция побитового копирования одного объекта в другой. После копирования указатели в обоих объектах будут иметь одинаковое значение. А это значит, что они будут указывать на одну и ту же область памяти. В самом этом факте ещё нет ничего страшного.
А теперь самое интересное! Когда функция завершается, то удаляются все временные переменные, и в этот момент будет вызван тот самый деструктор вашего класса, который произведёт "правильное" освобождение памяти по имеющемуся указателю. Теперь ваш оригинальный объект, который вы передали ранее в функцию по значению, является разрушенным: его указатель теперь указывает на область памяти, которая уже считается освобождённой.
Ошибка такого рода может создать много проблем, если не следить за конструкторами копирования. Поэтому всегда создавайте правильный конструктор копирования.
В случае с приведённым примером, в конструкторе копирования должна быть выделена новая область памяти для указателя.
Смотрите также:
Порядок выполнения конструктора и деструктора
Порядок выполнения конструктора и деструктора при наследовании
Порядок передачи переменной в функцию?
Как передаётся переменная в функцию?
Создаётся ли временная переменная, при вызове функции?
Данный пример демонстрирует ответ на ваши вопросы.
При вызове функции с передачей объекта по значению, происходит копирование объекта во временную переменную того же типа. При этом вызывается конструктор копирования. Если конструктор копий не найден в этом классе, то выполняется побитовое копирование объекта во временную переменную.
Пример ниже демонстрирует это:
#include <iostream>
using namespace std;
class A
{
public:
A(int _i) {
i = _i;
cout << "Constr A i=" << i << endl;
}
A(A &obj) {
i = obj.i + 1;
cout << "Copy-Constr A i=" << i << endl;
}
~A() {
cout <<
"Destr A i=" << i << endl;
}
A &operator=(A &obj) {
i = obj.i + 2;
cout << "Assignment A i=" << i << endl;
return *this;
}
int i;
};
void foo(A objA) {
cout << "Inside foo(); objA.i = " << objA.i << endl;
}
int main()
{
cout << "Begin Program\n";
cout << "\nCreate object A objA(1);\n";
A objA(1);
cout << "\nInvoke foo(objA);\n";
foo(objA);
cout << "\nEnd Program\n";
return 0;
}
Вывод:Begin Program Create object A objA(1); Constr A i=1 Invoke foo(objA); Copy-Constr A i=2 Inside foo(); objA.i = 2 Destr A i=2 End Program Destr A i=1
Обратите внимание на то, что отсутсвтие конструктора копий в классе не приводит к вызову определённого в этом классе оператора приваивания (operator =). Чтобы в этом убедиться, закомментируйте конструктор копий:
//...
// A(A &obj) {
// i = obj.i + 1;
// cout << "Copy-Constr A i=" << i << endl;
// }
//...
Теперь в классе нет конструктора копий, но есть переопределённый оператор присваивания. //...
A &operator=(A &obj) {
i = obj.i + 2;
cout << "Assignment A i=" << i << endl;
return *this;
}
//...
Не смотря на это, оператор присваивания не выполняется при создании временных переменных. И теперь вывод программы будет следующим:
Begin Program Create object A objA(1); Constr A i=1 Invoke foo(objA); Inside foo(); objA.i = 1 Destr A i=1 // Это удаляется временная переменная! End Program Destr A i=1 // Удаляется переменная из функции main()
Несколько слов о конструкторах копий.
Написание конструкторов копирования для ваших классов - ответственная задача. Особенно если ваш класс работает с указателями.Допустим, вы написали класс, который использует указатели, выделяет под них динамическую память, а потом корректно её освобождает по удалению объекта. Всё работает правильно, но вы не реализовали конструктор копирования.
В этом случае вы рискуете встретиться с весьма неявной ошибкой, когда попытаетесь передать объект своего класса в функцию по значению.
Дело в том, что при отсутствии конструктора копий, как было сказано выше, выполняется стандартная операция побитового копирования одного объекта в другой. После копирования указатели в обоих объектах будут иметь одинаковое значение. А это значит, что они будут указывать на одну и ту же область памяти. В самом этом факте ещё нет ничего страшного.
А теперь самое интересное! Когда функция завершается, то удаляются все временные переменные, и в этот момент будет вызван тот самый деструктор вашего класса, который произведёт "правильное" освобождение памяти по имеющемуся указателю. Теперь ваш оригинальный объект, который вы передали ранее в функцию по значению, является разрушенным: его указатель теперь указывает на область памяти, которая уже считается освобождённой.
Ошибка такого рода может создать много проблем, если не следить за конструкторами копирования. Поэтому всегда создавайте правильный конструктор копирования.
В случае с приведённым примером, в конструкторе копирования должна быть выделена новая область памяти для указателя.
Смотрите также:
Порядок выполнения конструктора и деструктора
Порядок выполнения конструктора и деструктора при наследовании
Комментариев нет:
Отправить комментарий