24 янв. 2009 г.

C++: явное и неявное создание объектов

Для эффективной работы C++ стоит хорошо понимать когда и при каких условиях создаётся объект: создание объекта (указателя на объект) через new, явное создание стековой переменной и неявное создание объекта, например при вызове метода с передачей параметра по значению и т.п.
Что касается передачи по значению для примитивов типа int (и им подобные) и возвращение по указателю тонкостей нет, в то время как передачу параметра по значению для сложных типов и передачу параметра по ссылке, следует хорошо понимать.

Пусть есть некоторый класс данных SmthData:
class SmthData 
{
public:
// конструктор по-умолчанию
SmthData(): value(0)
{
std::cout << "SmthData::SmthData()" << std::endl;
}

// копирующий конструктор
SmthData(const SmthData &other):value(other.value)
{
std::cout << "SmthData::SmthData(const SmthData&)" << std::endl;
}

int value;
}
замечание: Для лучшего понимания будем отслеживать явные и не явные моменты создания SmthData (для чего и пишется в std::out при вызове конструкторов).

И есть некоторый класс Foo, который должен возвращать SmthData:
class Foo 
{
public:

void print()
{
std::cout << "Foo::print data.value:" << data.value << std::endl;
}

// здесь будет функция getSmthData
private:
SmthData data;
};

замечание: При создании экземпляра класса Foo будет создан экземпляр класса SmthData (будет вызван конструктор по-умолчанию) в месте декларации поля data класса Foo:
private:
SmthData data;
Проверяем:
int main(int argc, char *argv[]) 
{
Foo foo;
return 0;
}
и запускаем:
$ g++ -W -Wall main.cpp -o main && ./main
SmthData::SmthData()


I. Первый вариант функции Foo::getSmthData: возврат по значению
  SmthData smthData()
{
return data;
}
Проверяем
  SmthData d = foo.smthData();
std::cout << "d.value:" << d.value << std::endl;
d.value = 5;
std::cout << "d.value:" << d.value << std::endl;
foo.print();
результат:
SmthData::SmthData(const SmthData&)
d.value:0
d.value:5
Foo::print data.value:0

Что произошло: в точке выхода из метода
return data;
была создана копия объекта посредством вызова копирующего конструктора - дальнейшие изменения d не приводят к изменению data экземпляра foo.

II. Второй вариант функции Foo::getSmthData: возврат ссылки
  SmthData &smthData()
{
return data;
}
Проверяем
  SmthData d = foo.smthData();
std::cout << "d.value:" << d.value << std::endl;
d.value = 5;
std::cout << "d.value:" << d.value << std::endl;
foo.print();
результат:
SmthData::SmthData(const SmthData&)
d.value:0
d.value:5
Foo::print data.value:0

Что произошло: метод вернул ссылку на свойство Foo::data, а после был вызван оператор присвоения объекта по значению
SmthData d = 
и была создана копия объекта посредством вызова копирующего конструктора - точно так же дальнейшие изменения d не приводят к изменению data экземпляра foo.

II'. Используем второй вариант функции Foo::getSmthData возврат ссылки, но изменим теперь код проверки:
  SmthData &d = foo.smthData();
std::cout << "d.value:" << d.value << std::endl;
d.value = 5;
std::cout << "d.value:" << d.value << std::endl;
foo.print();
результат:
d.value:0
d.value:5
Foo::print data.value:5

Что произошло: Мы получили прямую ссылку на свойство Foo::data и можем её модифицировать, при этом копия объекта не создавалась.

III. Третий вариант функции Foo::getSmthData: возврат константной ссылки
  const SmthData &smthData()
{
return data;
}
Проверяем
  const SmthData &d = foo.smthData();
std::cout << "d.value:" << d.value << std::endl;
foo.print();
результат:
d.value:0
Foo::print data.value:0

Что произошло: Мы получили прямую ссылку на свойство Foo::data и т.к ссылка является константой, невозможно модифицировать объект (компилятор не позволит скомпилировать код типа d.value = 5;), копия объекта также не создавалась.

дополненено замечание: конечно же, используя мощь и средства C/C++ никто не мешает изменить объект путём приведения константной ссылки к константному указателю, который привести к не константному и далее модифицировать объект - но это есть bad code style:
const SmthData *cp = &d; // получаем указатель (константный)
SmthData *p = const_cast<SmthData*>(cp); // снимаем "константность"
p->value = 5;


p.s. такое же поведение сохраняется и при вызове метода с передачей параметра по значению, и по ссылке.

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