от vesso(7-08-2005)

рейтинг (30)   [ добре ]  [ зле ]

Printer Friendly Вариант за отпечатване

Примерен код на cpp
/*
 *      Модерни методи за управление на паметта в C++
 *
 *   Една от особеностите на C++ е че дава почти пълен
 * контрол на програмиста кога и как да задели памет и кога
 * и как да я освободи. С този контрол идва и отговорността
 * да се освободи всяко "парче" памет което вече не е нужно.
 * Когато програмата не освобождава памет която вече не е
 * нужна се получава "утечка на памет" (memory leak).
 *
 *   Напоследък езици като java, perl и python станаха доста
 * популярни. Едно от основните им предимства е че спестяват
  * грижите за управление на паметта. С тях програмистите не
трябва
  * полагат усилия за освобождаване на памет която вече не е
нужна.
 * Нормално е програмист на C++, след като е загубил половин
  * ден търсейки защо програмата му консумира все повече и
повече
  * памет, да започне тайно да завижда на програмистите
ползващи
 * езици от по-високо ниво.
 *
  *   С няколко трика почти пълно безгрижие при управлението
на
  * паметта може да се постигне и в C++, при което
програмистът
  * постига най-доброто от двата свята - компилиран "до
метала"
 * код и избягване на рискове от утечки на паметта. Целта на
  * тази статия е да запозна читателя с тези трикове.
Програмистите
  * които вече ползват boost::shared_ptr (и знаят защо го
правят)
  * едва ли ще научат нещо ново - но това не би трябвало да
им пречи
 * да дадат гласа си за статията :-)
 *
  *   Статията не е предназначена за абсолютно начинаещи.
Предполага
 * се че читателят има базови познания за C++, std::vector,
  * std::stream и други подобни. По-напредналите читатели
могат
 * да пропуснат първата половина и да прескочат направо до
 * секцията за auto_ptr и shared_ptr.
 *
 *   Статията е оформена като програма на C++ и може да бъде
  * компилирана и изпълнявана стъпка по стъпка с цел
постигане на
  * максимална яснота. Кодът е под лиценз GPL V2, а
авторските
 * права са на Веселин Костадинов.
 *
  *   Статията-програма ползва библиотеката boost, достъпна
за
 * свободно ползване от www.boost.org. Библиотеката се
  * инсталира под debian/ubuntu с "apt-get install
libboost-dev",
  * за други архитектури и компилатори потърсете информация
на
 * техния сайт.
 *
  *   Ето и самата програма. Първо ще ни трябват няколко
header-а:
 */
 #include <iostream>
 #include <string>
 #include <vector>
 #include <sstream>
 #include <memory>
  #include
<boost/shared_ptr.hpp>

  // Ще ни трябва и един клас който
да ни казва когато се създават,
  // копират, използват и изтриват
обекти:
 class my_class {
         private:
         std::string m_name;
         public:
          my_class(const
std::string& name): m_name(name)
          {std::cout << "creating
 '" << m_name << "'\n";}
          my_class(const
 my_class& other): m_name("copy of
" + other.m_name)
          {std::cout << "creating
 '" << m_name << "'\n";}
          ~my_class() {std::cout << "deleting '" << m_name <<
"'\n";}
          void use(const std::string & usage)
const
          {std::cout << "Using
 '" << m_name << "':
 " << usage << "\n";}
 };

  // Ще ни трябват и 2 функции с
които да симулираме използване
  // на обекти: чрез референция и
чрез указател:
  void
use_by_reference(my_class & object)
 {
          object.use("from function
use_by_reference()");
 }

  void
use_by_pointer(my_class * object)
 {
          object->use("from function
use_by_pointer()");
 }

  //Ще ни трябва и функция която по
прявило връша false
  //а по изключение: true
(http://en.wikipedia.org/wiki/Blue_moon)
  //Ние наблягаме на изключенията и
затова я прявим винаги
 //да връща truer:
  bool is_blue_moon() {return true;}

  //Ще ни трябва и функция която в
редки случаи генерира 
  //изключение. Ние си падаме по
редките случаи и затова
  //ще я направим винаги да
генерира изключение:
 void may_throw_exception()
 {
          std::cout << "Throwing an
exception...\n";
          throw
std::string("exception");
 }

  /*   Дотук с подготовката.
Започваме по същество,
 *
 *   Една променлива в C++ може да се разположи в едно
 * от следните "места":
 *     - в регистрите на процесора
 *     - в статичната памет
 *     - в стека
 *     - в динамично заделена памет
 *
 *   Програмистът почти няма възможности да управлява
 * ползването на регистрите в C++ затова този метод
 * ще го оставим на мира. Там грешки не могат да
 * се допускат.
 *
 *   Разполагането на обекти в статичната памет се
 * извършва преди извикването на функцията main().
 * Същите обекти се разрушават след приключването
 * на main(). Програмистът няма контрол върху
 * управлението на статичната памет, следователно и
 * там грешки не могат да се допускат. Ето пример на
 * ползване на статична променлива: */
  my_class static_class("static
sample");

  /*   Променливите и обектите
разположени в стека (известни
  * и като локални променливи, както и като "auto") се
създават
 * в момента на декларирането им а се разрушават когато
 * програмата достигне до края на локалната област (scope),
 * обозначена със затваряща къдрава скоба. Понеже
 * паметта се освобождава автоматично, ползването
 * на локални променливи не води до утечки на памет.
 * Все пак тези променливи си имат своите ограничения.
 * Пример: */
 void auto_example()
 {
         std::vector<my_class> vector_of_objects;
         std::vector<my_class *> vector_of_pointers;
          my_class object("auto
 object");              //*1
          object.use("as object");
                    //*2
          use_by_reference(object);                    //*3
          use_by_pointer(&object);                    
//*4
          vector_of_objects.push_back(object);         //*5
          vector_of_pointers.push_back(&object);      
//*6
          for (int index = 2; index < 4;
++index) {
                 std::ostringstream oss;
                  oss << "auto
" << index;
                 my_class inner(oss.str());
                  vector_of_objects.push_back(inner);     
//*5, *7
                  vector_of_pointers.push_back(&inner);  
 //*6
         }//*8
         //9
          vector_of_objects[0].use("as
the first object in the vector");
          vector_of_objects[2].use("as
the last object in the vector");
          vector_of_pointers[0]->use("as the first pointer in the vector");
          //vector_of_pointers[1]->use(""); -
би било грешка!
 }

 /*Наблюдения:
 * 1, 2, 3, 4 локалните променливи са лесни за употреба
  * 5 Може би си мислим че поставяме обекта object в
контейнера
 *    Истината е че компилаторът създава копие на object и
 *    поставя копието в контейнера. При сложни обекти
 *    създаването на копие може да е доста бавна операция
 * 6 Можем да ползваме вектор от указатели - тогава няма
 *    копиране на обекти. Но и това не винаги е удачно.
 * 7 std::vector има неприятното свойство от време на
 *   време да си променя размера, при което заделя нова
 *   памет, копира всички обекти в нея и разрушава
 *   обектите които първоначално е съдържал. При голям
 *   вектор съдържащ сложни обекти това може да отнеме
 *   неочаквано много време!
 * 8 При достигане на края на локалната област всички обекти
 *   създадени в нея се разрушават. В този момент
 *   vector_of_pointers съдържа указатели към вече
 *   разрушени обекти!
 * 9 След края на вътрешния цикъл копията на обекта inner
 *   все още са достъпни за употреба
 * 10 (не демонстрирано) понякога има ограничение на размера
 *   на стека който е достъпен за програмите. Например
 *   собственическите драйвери на NVidia не вървят на
 *   кернел с 4 KB стек - явно програмистите на NVidia са
 *   се поувлекли в ползването на локални променливи.
 *
 *   За да се решат горепосочените проблеми се използва
 * динамично заделяне на памет когато е необходима. Това
 * налага съответно освобождавана когато паметта не е
 * необходима. Пример: */

 void dynamic_example()
 {
          my_class * pointer(new my_class("dynamically allocated"));
         std::vector<my_class *> vector_of_pointers;
         vector_of_pointers.push_back(pointer);
          pointer->use("as
pointer");
         use_by_reference(*pointer);
         use_by_pointer(pointer);
          vector_of_pointers[0]->use("as the first pointer in the vector");
         delete pointer;
 }

 /*
  *   Обектите създадени с new се разполагат в динамичната
памет.
  * Те остават и след края на локалната област (skope).
Поставяне
  * на указатели към тези обекти в контейнери е значително
по-бърз
  * процес. Разрушаването на тези обекти е задължение на
програмиста
 * и пример за такова разрушаване е последния ред.
  *   Когато по една или друга причина операторът delete не
се
  * изпълни за така създаден обект се получава утечка на
памет.
 * Примери: */
 void simple_leak()
 {
          my_class * pointer(new my_class("*simple leak"));
 }

 void another_simple_leak()
 {
          my_class * pointer(new my_class("*another simple leak"));
          pointer = new
my_class("another object...");
          //след горния ред вече е
невъзможно да се освободи паметта 
          //заделена за обект
"*another simple leak"
         delete pointer;
 }

 void conditional_leak()
 {
          my_class * pointer(new my_class("*conditional leak"));
         //100 реда код
         if (is_blue_moon())
         return;
         //още 100 реда код
         delete pointer;
 }

 void leak_on_exception()
 {
          my_class * pointer(new my_class("*leak on exception"));
         may_throw_exception();
         delete pointer;
 }

 /*
  *   Както е видно от резултата от програмата, горните 4
обекта
  * се създават но никога не се разрушават. Паметта заета от
тях
 * остава неизползваема за други цели до приключване на
  * програмата. Ако една такава функция се изпълнява в цикъл
то
 * за кратко време цялата памет на системата (в рамките на
 * лимитите - ако има такива) ще бъде неизползваема и това
 * рядко е хубаво нещо.
 *
  *   Някои програмисти на C++ все още ползват
malloc()/free()
 * за управление на паметта. Няма да давам примери с тези
  * функции защото (1) те са архаичен остатък от C, (2) може
да
  * се пише C++ без въобще да се ползват тези функции и (3)
всеки
 * програмист на C++ който продължава да ги ползва заслужава
 * лек електрошок.
 *
 *   И така стигаме до същността на статията.
 *
 *   Първият от модерните методи за управление на паметта
 * е std::auto_ptr. Ползвайки го можем да избегнем
 * гореописаните утечки, защото при излизането от локалната
 * област auto_ptr гарантирано унищожава обекта който е
 * бил създаден: */
 void simple_autoptr()
 {
          std::auto_ptr<my_class> pointer(new my_class("simple auto_ptr"));
          //примери за употреба на
обекти контролирани с auto_ptr:
          pointer->use("as
pointer");
         use_by_pointer(pointer.get());
         use_by_reference(*pointer);
 }

 void conditional_autoptr()
 {
          std::auto_ptr<my_class> pointer(new my_class("conditional auto_ptr"));
         if (is_blue_moon())
         return;
          // auto_ptr не се нуждае
от "delete pointer;"
          // но ако има нужда
програмистът може да освободи паметта 
         // предварително:
          pointer.reset(); // това
не се изпълнява
 }

  void
autoptr_and_exception()
 {
          std::auto_ptr<my_class> pointer(new my_class("auto_ptr and exception"));
         may_throw_exception();
 }

 /*
  *   Забележете че всички обекти създадени с auto_ptr са
надлежно разрушени.
  * По-подробна статия за ползване на auto_ptr (на английски)
има на:
  * //http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
 *
  *   auto_ptr върши добра работа когато искаме да поставим
обектите
  * в динамичната памет вместо в стека, но си има и своите
недостатъци.
  * Най-сериозният от тях е че auto_ptr указатели не могат да
се
 * слагат в STL контейнери.
 *
  *   boost::shared_ptr е един по-умен автоматичен указател.
Той
  * помни колко пъти е копиран и когато броячът на копията
стане
 * 0 обектът се самоизтрива. boost::shared_ptr обекти могат
  * да се поставят в контейнери. Te могат да останат да
съществуват
  * след края на локалната област в случай че са нужни
другаде.
 *   Пример: */

 void shared_ptr_demo()
 {
          typedef
 boost::shared_ptr<my_class> sp_my_class;     //*1
         std::vector<sp_my_class> vector;
          sp_my_class example1(new my_class("the first shared_ptr object"));
          example1->use("as
pointer");
         use_by_pointer(example1.get());
         use_by_reference(*example1);
          vector.push_back(example1);                        
 //*2
          example1.reset();                                  
 //*3
          for (int index = 2; index < 4;
++index) {
                 std::ostringstream oss;
                  oss << "shared_ptr " << index;
                  sp_my_class inner(new my_class(oss.str()));
                  vector.push_back(inner);                   
     //*2
         }
          vector[0]->use("as
vector[0]");
          vector[1]->use("as
 vector[1]");                      //*4
          sp_my_class example2(new my_class("another shared_ptr object"));
          example2 = vector[2];                              
 //*5
         may_throw_exception();
  }                                                       
//*6

 /*
 *   Коментари:
 * 1 Често е удобно веднага след дефиницията на класа да се
 *   дефинира и shared_ptr към същия клас
  * 2 Поставяне на обекти контролирани с shared_ptr в
контейнери
 *   не води до копиране на обектите какъвто беше случая с
 *   auto обектите по-горе
  * 3 Ако държим да обявим че example1 вече не сочи към обект
може
  *   да ползваме reset(). Ако няма друга референция към
обекта
  *   той ще бъде унищожен, но в нашия случай обектът е
сложен в
  *   контейнер и ще продължи да заема памет докато има нужда
от
 *   него, т.е. докато контейнерът не бъде разрушен.
 * 4 shared_ptr обектите останаха активни и след края на
 *   локалната област
 * 5 На shared_ptr указатели могат да се присвояват други
 *   стойности без това да води до утечки на памет.
 * 6 При излизане от функцията всички обекти се унищожават
 *   защото от тях няма повече нужда. Дори и генерираното
 *   изключение не може да заблуди shared_ptr.
 *
 *   shared_ptr обектите имат и няколко особености с които
 * трябва да се съобразяваме:
 * 1. Недейте да създавате циркулярни вериги. Пример:
 *   - Обект А съдържа shared_ptr към себе си
 *   - Обект А съдържа shared_ptr към обект Б и обект Б
 *     съдържа shared_ptr към обект А
 *   - Обект А съдържа shared_ptr към обект Б и обект Б
 *     съдържа shared_ptr към обект В, ... обект Ъ
 *     съдържа shared_ptr към обект А
 *   Решения: Ползвайте STL контейнери или boost::weak_ptr.
 * 2. Недейте да използвате временни shared_ptr обекти
 *   като параметри на функции а винаги създавайте
 *   именовани shared_ptr обекти. Пример (от документацията
 *   на boost):
 void f(shared_ptr, int){}
 int g(){}

 void ok()
 {
 shared_ptr p(new int(2));
 f(p, g());
 }

 void bad()
 {
 f(shared_ptr(new int(2)), g());
 }
 *   Тъй като поредността на изчисляване на аргументите
 * на функцията е неопределен, възможно е първо да се
 * извика g(), тя да генерира изключение още преди
 * конструктора на shared_ptr обекта да е бил извикан.
 * Аз лично си имах разправии с един компилатор който
 * в дебъг режим създава код който изчислява параметрите
 * по един начин, а в нормален режим - по друг. Програмата
 * вървеше идеално в дебъгера ми но като се компилира
 * като за пред клиент се държеше много зле.
 *
 *   В заключение: Ако пишете програми които:
 *     - работят с голям брой обекти
 *     - разполагат тези обекти в контейнери
 *     - не трябва да имат утечки на памет
 *     - трябва да имат прилично бързодействие
 *  бих Ви препоръчал да управлявате със shared_ptr всеки
 *  обект по-голям от десетина байта. Съобразявайте се
 *  със особеностите на shared_ptr и мъчителното търсене
 *  на утечки на памет остава в историята.
 *
 *  Ето и главната програма: */
 int main()
 {
          std::cout << "program
started.\n";
         auto_example();
         dynamic_example();
         simple_leak();
         another_simple_leak();
         conditional_leak();
         try {
                 leak_on_exception();
          } catch (const std::string exception) {
                  std::cout << "caught exception\n";
         }
         simple_autoptr();
         conditional_autoptr();
         try {
                 autoptr_and_exception();
          } catch (const std::string exception) {
                  std::cout << "caught second exception\n";
         }
         try {
                 shared_ptr_demo();
          } catch (const std::string exception) {
                  std::cout << "caught third exception\n";
         }

          std::cout << "program
ended.\n";
         return 0;
 }

 /* Резултат от изпълнението:

 creating 'static sample'
 program started.
 creating 'auto object'
 Using 'auto object': as object
 Using 'auto object': from function use_by_reference()
 Using 'auto object': from function use_by_pointer()
 creating 'copy of auto object'
 creating 'auto 2'
 creating 'copy of copy of auto object'
 creating 'copy of auto 2'
 deleting 'copy of auto object'
 deleting 'auto 2'
 creating 'auto 3'
 creating 'copy of copy of copy of auto object'
 creating 'copy of copy of auto 2'
 creating 'copy of auto 3'
 deleting 'copy of copy of auto object'
 deleting 'copy of auto 2'
 deleting 'auto 3'
  Using 'copy of copy of copy of auto object': as the first
object in the vector
 Using 'copy of auto 3': as the last object in the vector
 Using 'auto object': as the first pointer in the vector
 deleting 'auto object'
 deleting 'copy of copy of copy of auto object'
 deleting 'copy of copy of auto 2'
 deleting 'copy of auto 3'
 creating 'dynamically allocated'
 Using 'dynamically allocated': as pointer
  Using 'dynamically allocated': from function
use_by_reference()
  Using 'dynamically allocated': from function
use_by_pointer()
  Using 'dynamically allocated': as the first pointer in the
vector
 deleting 'dynamically allocated'
 creating '*simple leak'
 creating '*another simple leak'
 creating 'another object...'
 deleting 'another object...'
 creating '*conditional leak'
 creating '*leak on exception'
 Throwing an exception...
 caught exception
 creating 'simple auto_ptr'
 Using 'simple auto_ptr': as pointer
 Using 'simple auto_ptr': from function use_by_pointer()
 Using 'simple auto_ptr': from function use_by_reference()
 deleting 'simple auto_ptr'
 creating 'conditional auto_ptr'
 deleting 'conditional auto_ptr'
 creating 'auto_ptr and exception'
 Throwing an exception...
 deleting 'auto_ptr and exception'
 caught second exception
 creating 'the first shared_ptr object'
 Using 'the first shared_ptr object': as pointer
  Using 'the first shared_ptr object': from function
use_by_pointer()
  Using 'the first shared_ptr object': from function
use_by_reference()
 creating 'shared_ptr 2'
 creating 'shared_ptr 3'
 Using 'the first shared_ptr object': as vector[0]
 Using 'shared_ptr 2': as vector[1]
 creating 'another shared_ptr object'
 deleting 'another shared_ptr object'
 Throwing an exception...
 deleting 'the first shared_ptr object'
 deleting 'shared_ptr 2'
 deleting 'shared_ptr 3'
 caught third exception
 program ended.
 deleting 'static sample'
 */

P.S. По някаква причина в режим "преглед" статията изглеждаше добре, а след публикуването някои редове се преформатираха.
Оригиналният текст е го има на:
http://home.exetel.com.au/v/test1.cpp
Кирилицата е на utf8.
Моля уеб-мастъра да оправи процеса на публикуване - ако въобще е възможно.

P.P.S Статията изглежда добре във "Вариант за отпечатване"


<< Програмиране под Линукс. Въведение за начинаещи. | T2 Project има нужда от помощ >>