четверг, 1 марта 2012 г.

Примеры использования shared_ptr

   Продолжаем тему умных указателей. Рассмотрим подробнее shared_ptr. Основное, что можно сказать о shared_ptr, это то, что объект, на который указывают shared_ptr-ы, будет удален, когда самый последний shared_ptr будет уничтожен или сброшен с помощью reset().
void f(){
  boost::shared_ptr<A> ptr1(new A);
  boost::shared_ptr<A> ptr2 = ptr1;
  boost::shared_ptr<A> ptr3 = ptr1;
}
Иллюстрация к коду:
















Вот ещё несколько примеров, работы с shared_ptr:

  // Снятие константности
  boost::shared_ptr<const A> ptr(new const A);
  boost::shared_ptr<A> c(ptr, boost::detail::const_cast_tag());

  // Повышающее приведение типов
  boost::shared_ptr<B> ptr2(new B);
  boost::shared_ptr<A> c2(ptr2, boost::detail::dynamic_cast_tag());

  // Понижающее приведение типов
  boost::shared_ptr<A> ptr3(new B);
  boost::shared_ptr<B> c3(ptr3, boost::detail::dynamic_cast_tag());

  // Принудительное преобразование
  boost::shared_ptr<void> ptr4(new int);
  boost::shared_ptr<int> c4(ptr4, boost::detail::static_cast_tag());
   А вот пример, как надо писать и как не надо писать функции(выдержка из документации), соответственно, (good(), bad()):

void f2(boost::shared_ptr<A> p, int y){
  std::cout << *p << " = " << y << "\n";
}

int g(){
  std::cout << __PRETTY_FUNCTION__ << std::endl;
  //throw std::exception();
  return 8;
}

void bad(){
  f2(boost::shared_ptr<A>(new A(5)), g());
}

void good{
  shared_ptr<A> p(new A(5));
  f2(p, g());
}

   Что плохого в bad()? Там в аргументах f2() передается временный объект shared_ptr-а. Так как порядок вычисления аргументов неопределен, то, возможно, у Вас первым вычислится сначала g(), а потом уже создастся новый объект A(5), и вызовется конструктор shared_ptr<A>. Но, вдруг будет наоборот? Сначала создастся A(5), потом вызовется g() и в g() вылетит исключение, тогда конструктор shared_ptr не успеет вызваться и будет утечка памяти. В функции good() такой ситуации произойти не может. 

Иллюстрации к вышесказанному: 




P.S. Этой проблемы можно избежать, если использовать make_shared and allocate_shared:

boost::shared_ptr<A> ptr = boost::make_shared<A>(5); 
   Лично у меня такого не наблюдается. В стандарте написано, что порядок вычисления аргументов функции не определен (The order of evaluation of arguments is unspecified. All side effects of argument expression evaluations take effect before the function is entered. The order of evaluation of the postfix expression and the argument expression list is unspecified.). На моей архитектуре с моим компилятором порядок вычисления аргументов функции справа налево: сначала вызывается g(), а потом new A(5) и конструктор shared_ptr. Для проверки я поменяла местами аргументы: стало вызывается сначала new A(5), shared_ptr, а потом уже g(). И если выскакивает исключение, то всё работает правильно - A(5) удаляется. Ведь конструктор shared_ptr успел создать объект, а при раскрутке стека нам гарантируется, что для всех таки объектов будет вызван деструктор. НО, у shared_ptr деструктор не определен, у него он создается компилятором (деструктор по умолчанию). А кто же очищает память? - спросите Вы. Всё логично, очистка памяти происходит в деструкторе счетчика ссылок (shared_count).
   Но, описанная в Документации boost о shared_ptr ситуация с утечкой памяти возможна - см. Herb Sutter's treatment.


   Для компиляции предыдущих фрагментов необходим следующий код:
#include <iostream>
#include <fstream>
#include<boost/shared_ptr.hpp>

class A{
public:
  A(){}
  A(int a) : a_(a){
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
 virtual ~A(){
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
  friend std::ostream& operator << (std::ostream& os, const A&);
  int a_;
};

std::ostream& operator << (std::ostream& os, const A& a){
  os << a.a_;
  return os;
}

class B: public A{
public:
  B(){
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
  virtual ~B(){
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

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