суббота, 10 декабря 2011 г.

Передача переменных по значению в функцию

Возможно, вы задавались следующими вопросами:
Порядок передачи переменной в функцию?
Как передаётся переменная в функцию?
Создаётся ли временная переменная, при вызове функции?

Данный пример демонстрирует ответ на ваши вопросы.


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

Пример ниже демонстрирует это:
#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()

Несколько слов о конструкторах копий.

Написание конструкторов копирования для ваших классов - ответственная задача. Особенно если ваш класс работает с указателями.

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

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

Дело в том, что при отсутствии конструктора копий, как было сказано выше, выполняется стандартная операция побитового копирования одного объекта в другой. После копирования указатели в обоих объектах будут иметь одинаковое значение. А это значит, что они будут указывать на одну и ту же область памяти. В самом этом факте ещё нет ничего страшного.

А теперь самое интересное! Когда функция завершается, то удаляются все временные переменные, и в этот момент будет вызван тот самый деструктор вашего класса, который произведёт "правильное" освобождение памяти по имеющемуся указателю. Теперь ваш оригинальный объект, который вы передали ранее в функцию по значению, является разрушенным: его указатель теперь указывает на область памяти, которая уже считается освобождённой.

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

В случае с приведённым примером, в конструкторе копирования должна быть выделена новая область памяти для указателя.


Смотрите также:
Порядок выполнения конструктора и деструктора
Порядок выполнения конструктора и деструктора при наследовании

Комментариев нет:

Отправить комментарий