Контейнеры STL. Реализация ссылочной семантики

   
На этом шаге мы рассмотрим реализацию ссылочной семантики.

   
Контейнерные классы STL поддерживают семантику значений, но не поддерживают ссылочную семантику. Они создают
внутренние копии вставляемых элементов и затем возвращают эти копии. На 119 шаге рассматриваются
достоинства и недостатки обеих семантик, а также некоторые последствия, к которым приводит выбор. Напоминаем один из выводов:
если вам потребуется ссылочная семантика в контейнерах STL (например, из-за того, что копирование элементов обходится
слишком дорого или же элементы должны совместно использоваться несколькими коллекциями), воспользуйтесь классом умного
указателя для предотвращения потенциальных ошибок. Ниже приведено одно из возможных решений проблемы. В нем задействован
вспомогательный класс умного указателя с подсчетом ссылок на объекты, на которые ссылается указатель:

#ifndef COUNTED_PTR_HPP 
#define COUNTED_PTR_HPP
// Класс, обеспечивающий семантику подсчета ссылок
// - объект, на который ссылается указатель, автоматически
// уничтожается при удалении последнего экземпляра CountedPtr
// для данного объекта.

template <class T> 
class CountedPtr { 
  private:
    T* ptr;         // Указатель на значение
    long* count;    // Количество владельцев (общие данные)
  public:
    // Инициализация объекта существующим указателем 
    // - указатель р должен быть лолучен в результате вызова new 
    explicit CountedPtr (T* p=0) 
      : ptr(p), count(new long(1)) {}

    // Копирующий указатель (увеличивает счетчик владельцев) 
    CountedPtr (const CountedPtr<T>& p) throw() 
       : ptr(p.ptr), count(p.count) { 
      ++*count;
    }

    // Деструктор (уничтожает объект, если владелец был последним) 
    ~CountedPtr () throw() { 
      dispose();
    }

    // Присваивание (перевод указателя на новый объект) 
    CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() { 
      if (this != &p) {
        dispose();
        ptr = p.ptr;
        count = p.count;
        ++*count;
      }
      return *this;
    }

    // Доступ к объекту, на который ссылается указатель 
    T& operator*() const throw() { 
      return *ptr;
    }
    T* operator->() const throw() { 
      return ptr;
    }

  private: 
    void dispose() {
      if (--*count == 0) { 
        delete count; 
        delete ptr;
      }
    }
};
#endif //COUNTED_PTR_HPP

   
Класс напоминает стандартный класс умного указателя auto_ptr (смотри 59 шаг). Предполагается, что
значения, которыми инициализируются умные указатели, были возвращены оператором new. В отличие от класса auto_ptr это
позволяет копировать умные указатели так, чтобы оригинал и копия оставались действительными. Объект уничтожается только после
удаления последнего указателя, ссылающегося на него.

   
Класс CountedPtr можно усовершенствовать, например реализовать в нем автоматическое преобразование типов или
возможность передачи права владения от умного указателя вызывающей стороне.

   
Ниже приведен пример использования класса CountedPtr:

//---------------------------------------------------------------------------

#include <vcl.h>
#include <iostream>
#include <list>
#include <deque>
#include <algorithm>

#include "countptr.hpp"
#include <conio.h> //необходимо для getch()

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;


std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

void printCountedPtr (CountedPtr<int> elem)
{
  cout << *elem << ' ';
}

int main(int argc, char* argv[])
{
  // Массив целых чисел (для совместного использования
  // в разных контейнерах)
  static int values[] = { 3, 5, 9, 1, 6, 4 };

  // Две разные коллекции 
  typedef CountedPtr<int> IntPtr; 
  deque<IntPtr> coll1; 
  list<IntPtr> coll2;

  // Вставка общих объектов в коллекции
  // - исходный порядок в coll1
  // - обратный лорядок в coll2

  for (int i=0; i<sizeof(values)/sizeof(values[0]); ++i) { 
    IntPtr ptr(new int(values[i])); 
    coll1.push_back(ptr); 
    coll2.push_front(ptr);
  }

  // Вывод содержимого обеих коллекций 
  cout << ToRus("Коллекция coll1:\n");
  for_each (coll1.begin(),coll1.end(),  
            printCountedPtr); 
  cout << endl;

  cout << ToRus("Коллекция coll2:\n");
  for_each (coll2.begin(), coll2.end(),
            printCountedPtr); 
  cout << endl << endl;

 // Модификация значений в разных коллекциях
 // - возведение в квадрат третьего значения в coll1
 // - изменение знака первого значения в coll1
 // - обнуление первого значения в coll2

 *coll1[2] *= *coll1[2]; 
  (**coll1.begin()) *= -1; 
  (**coll2.begin()) = 0;

  // Повторный вывод содержимого обеих коллекций 
  cout << ToRus("Коллекция coll1 после изменения:\n");
  for_each (coll1.begin(),coll1.end(),  
            printCountedPtr); 
  cout << endl;

  cout << ToRus("Коллекция coll2 после изменения:\n");
  for_each (coll2.begin(), coll2.end(),
            printCountedPtr); 
  cout << endl << endl;

  getch();
  return 0;
}

//---------------------------------------------------------------------------

Текст этого примера можно взять здесь.

   
Результат выполнения программы выглядит так:


Рис.1. Результат работы приложения

   
Если вызвать вспомогательную функцию, сохраняющую элемент коллекции (IntPtr) где-то в другом месте, то значение, на
которое ссылается указатель, остается действительным даже после уничтожения коллекций или удаления из них всех элементов.

   
В архиве Boost библиотек C++ (http://www.boost.org/) хранятся различные
классы умных указателей, расширяющие стандартную библиотеку C++ (вероятно, вместо CountedPtr<> стоит
поискать название shared_ptr<>).

   
На следующем шаге мы приведем рекомендации по выбору контейнера.



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

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