Re[6]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 29.07.10 08:37
Оценка: +1 -1
ononim wrote:

> ЗЫ А меня от всего МФСшного, включая CList несварение желудка случается.

> Более отстойной библиотеки чем MFC пожалуй не найти.

Меня тоже от CList передёргивает, но попробуй на STD напиши коллекцию-
хранилище полиморфных объектов, которое бы само удаляло объекты
при удалении их из хранилища. (shared-ptr не предлагать — +4 байта на
каждый хранимый объект не катит). А на MFC-шных -- в момент пишется.

STL не различает ситуацию удаления элемента из коллекции и ситуацию
перенесения объекта из одного буфера коллекции в другой (новый).
Потому что академичный, блин.
Posted via RSDN NNTP Server 2.1 beta
Re[7]: Зачем нужен итератор?
От: ononim  
Дата: 29.07.10 10:04
Оценка: -1
MZ>Меня тоже от CList передёргивает, но попробуй на STD напиши коллекцию-
MZ>хранилище полиморфных объектов, которое бы само удаляло объекты
MZ>при удалении их из хранилища. (shared-ptr не предлагать — +4 байта на
MZ>каждый хранимый объект не катит). А на MFC-шных -- в момент пишется.

сходу — как-то так:
template <class T> class ZuPtr
{
    mutable T *_t;
public:
    ZuPtr(T *t):_t(t) {}
    ZuPtr(const ZuPtr &z):_t(z._t) {z._t = 0;}
    ZuPtr &operator =(const ZuPtr &z){_t = z._t; z._t = 0; return *this;}
    ~ZuPtr() {delete _t;}
    T* operator ->(){return _t;}
};

struct Foo
{
    Foo(){printf("[0x%x] Foo\n", this);}
    ~Foo(){printf("[0x%x]~Foo\n", this);}
};

typedef ZuPtr<Foo> FooPtr;

typedef std::vector<FooPtr> FooV; 
typedef std::list<FooPtr> FooL;


но вообще последнний раз когда мне требовалась такая задача мне нужен был map
и памяти было жалко, потому я сделал все на boost::intrusive_ptr
Как много веселых ребят, и все делают велосипед...
Re[8]: Зачем нужен итератор?
От: night beast СССР  
Дата: 29.07.10 10:12
Оценка:
Здравствуйте, ononim, Вы писали:

O>сходу — как-то так:

O>template <class T> class ZuPtr
O>{
O>    mutable T *_t;
O>public:
O>    ZuPtr(T *t):_t(t) {}
O>    ZuPtr(const ZuPtr &z):_t(z._t) {z._t = 0;}
O>    ZuPtr &operator =(const ZuPtr &z){_t = z._t; z._t = 0; return *this;}
O>    ~ZuPtr() {delete _t;}
O>    T* operator ->(){return _t;}
O>};

O>struct Foo
O>{
O>    Foo(){printf("[0x%x] Foo\n", this);}
O>    ~Foo(){printf("[0x%x]~Foo\n", this);}
O>};

O>typedef ZuPtr<Foo> FooPtr;

O>typedef std::vector<FooPtr> FooV; 
O>typedef std::list<FooPtr> FooL;


в std алгоритмах бывает используются временные переменные, так что твое решение не катит.
формально, задачу решает наследование от std::vector<FooPtr> и нужного определение деструктора.
Re[7]: Зачем нужен итератор?
От: zitz  
Дата: 29.07.10 14:01
Оценка: :)
Здравствуйте, Юрий Жмеренецкий, Вы писали:

MZ>>Вот кстати да. Почему бы не сделать было вместо итераторов

MZ>>заклад на 3 функции :
MZ>>-- size()
MZ>>-- T& operator [] (unsigned n)
MZ>>-- const T& operator [] (unsigned n) const

MZ>>Ничем не хуже.


ЮЖ>В случае, например списка, сложноть 'T& operator [] (unsigned n)' будет линейной, поэтому сложность перебора всех элементов с помощью этого интерфейса будет квадратичной.


Я делал себе такую штуку для множества, достаточно ввести m_nNextIndex и последовательный перебор всех значений (а у меня в основном такой) будет иметь обычную сложность.
Эх, люблю я цикл for( int i=0; i<set.GetCount(); i++) set[i];
UINT CXXXUIntSet::operator []( int nIndex )
{
    ASSERT( nIndex < GetSize() );
    std::set<UINT>* pSet1 = (std::set<UINT>*)m_pData;

    UINT retData = 0;

    if ( m_nNextIndex < 0 )
    {
        *(std::set<UINT>::iterator*)m_pNextPosition = pSet1->begin();
        m_nNextIndex = 0;
    }

    if ( nIndex == m_nNextIndex )
    {
        retData = **(std::set<UINT>::iterator*)m_pNextPosition;
        (*(std::set<UINT>::iterator*)m_pNextPosition)++;
        *(std::set<UINT>::iterator*)m_pNextPosition != pSet1->end() ? m_nNextIndex++ : m_nNextIndex = -1;
    }
    else
    {
        *(std::set<UINT>::iterator*)m_pNextPosition = pSet1->begin();
        m_nNextIndex = 0;
        while ( nIndex >= m_nNextIndex )
        {
            retData = **(std::set<UINT>::iterator*)m_pNextPosition;
            (*(std::set<UINT>::iterator*)m_pNextPosition)++;
            m_nNextIndex++;
        }
    }

    return retData;
}
... << RSDN@Home 1.2.0 alpha rev. 786>>
Re[9]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 29.07.10 14:53
Оценка:
night beast wrote:

> O> ZuPtr(T *t):_t(t) {}

> O> ZuPtr(const ZuPtr &z):_t(z._t) {z._t = 0;}
> O> ZuPtr &operator =(const ZuPtr &z){_t = z._t; z._t = 0; return *this;}
> O> ~ZuPtr() {delete _t;}
> O> T* operator ->(){return _t;}

> в std алгоритмах бывает используются временные переменные, так что твое

> решение не катит.

А чем они мешают ... что-то не соображу.
Но могу сказать точно: первое расширение содержимого vesctor-а
вызовит деструкторы в старом "хранилище" и указатели в новом хранилище
уже будут невалидные -- все объекты будут удалены.

> формально, задачу решает наследование от std::vector<FooPtr> и нужного

> определение деструктора.

Этого, блин, МАЛО. Нужно ещё заставить различаться ситуацию удаления
элементов НАСОВСЕМ от переноса их в новое хранилище.
Я ничего кроме написания собственного аллокатора с состоянием не
придумал. Решение непереносимо и ужасно.
Posted via RSDN NNTP Server 2.1 beta
Re[7]: Зачем нужен итератор?
От: Кодт Россия  
Дата: 29.07.10 17:23
Оценка:
Здравствуйте, MasterZiv, Вы писали:

MZ>Меня тоже от CList передёргивает, но попробуй на STD напиши коллекцию-

MZ>хранилище полиморфных объектов, которое бы само удаляло объекты
MZ>при удалении их из хранилища. (shared-ptr не предлагать — +4 байта на
MZ>каждый хранимый объект не катит). А на MFC-шных -- в момент пишется.

<a href=http://www.boost.org/doc/libs/1_43_0/libs/ptr_container/doc/ptr_container.html&gt;boost::ptr_vector&lt;/a&gt;? И никакого оверхеда.

MZ>STL не различает ситуацию удаления элемента из коллекции и ситуацию

MZ>перенесения объекта из одного буфера коллекции в другой (новый).
MZ>Потому что академичный, блин.

Скорее, потому что в стандарте 98 нет move constructor.
А MFC-шные фокусы с memmove — имеют ограниченную применимость.

Но, если очень хочется, можно сделать руками.
vector<T> u, v;
....
// переносим задний элемент из u в v

// копированием (дорого)
v.push_back(u.back());
u.pop_back();

// обменом
v.push_back(T());
swap(u.back(), v.back();
u.pop_back();
Перекуём баги на фичи!
Re[8]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 29.07.10 17:47
Оценка:
Кодт wrote:

> Скорее, потому что в стандарте 98 нет move constructor.


Так почему было либо не добавить его туда
либо не использовать просто функцию ?

> А MFC-шные фокусы с memmove — имеют ограниченную применимость.


Ни разу ещё на это не напарывался.
Впрочем, я в своём коде давно отказался от использования MFC-шных
коллекций.

> Но, если очень хочется, можно сделать руками.


Я не про это. Я про перенос ЭЛЕМЕНТОВ коллекций из одного
хранилища в другое. При переносе вызываются конструктор
в новом месте (копирования) и деструктор в старом месте.
В случае удаления -- просто деструктор. Ну и как
определить, что элемент из коллекции удаляют насовсем ?
Posted via RSDN NNTP Server 2.1 beta
Re[8]: Зачем нужен итератор?
От: Centaur Россия  
Дата: 29.07.10 18:09
Оценка: +1
Поздравляю, ononim, Вы изобрели std::auto_ptr<T>:

O>    ZuPtr(const ZuPtr &z):_t(z._t) {z._t = 0;}
O>    ZuPtr &operator =(const ZuPtr &z){_t = z._t; z._t = 0; return *this;}


Только без защиты от использования с стандартными контейнерами, которые требуют от элементов, чтобы они были CopyConstructible и CopyAssignable, с обязательной эквивалентностью копий.
Re[9]: Зачем нужен итератор?
От: ononim  
Дата: 29.07.10 18:20
Оценка:
C>Поздравляю, ononim, Вы изобрели std::auto_ptr<T>:
я знаю
Как много веселых ребят, и все делают велосипед...
Re[9]: Зачем нужен итератор?
От: Кодт Россия  
Дата: 29.07.10 18:44
Оценка: 3 (1)
Здравствуйте, MasterZiv, Вы писали:

>> Скорее, потому что в стандарте 98 нет move constructor.


MZ>Так почему было либо не добавить его туда либо не использовать просто функцию ?


Добавить КУДА? в интерфейс произвольного объекта (рядом с ctor, cctor, dtor) или в интерфейс контейнера?

>> А MFC-шные фокусы с memmove — имеют ограниченную применимость.


MZ>Ни разу ещё на это не напарывался.

MZ>Впрочем, я в своём коде давно отказался от использования MFC-шных коллекций.

>> Но, если очень хочется, можно сделать руками.


MZ>Я не про это. Я про перенос ЭЛЕМЕНТОВ коллекций из одного хранилища в другое.

MZ>При переносе вызываются конструктор в новом месте (копирования) и деструктор в старом месте.
MZ>В случае удаления -- просто деструктор.
MZ>Ну и как определить, что элемент из коллекции удаляют насовсем ?

А его в обоих случаях удаляют насовсем.
Просто, вместо глубокого копирования и глубокого разрушения можно применить трюк с созданием пустого объекта и обменом содержимым.
Для POD-типов и некоторых других вместо обмена можно применить memmove, что, собственно, CArray и делает.

Или ты имеешь в виду именно списки?
Так у std::list есть функция splice, как раз для того, чтобы переносить цепочку узлов из одного контейнера в другой, без конструирования-разрушения.
Перекуём баги на фичи!
Re[10]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 29.07.10 20:09
Оценка:
Кодт wrote:

> Добавить КУДА? в интерфейс произвольного объекта (рядом с ctor, cctor,

> dtor) или в интерфейс контейнера?

Да в стандарт. STL добавили, а

> А его в обоих случаях удаляют насовсем.


Здрасте, приехали.

> Просто, вместо глубокого копирования и глубокого разрушения можно

> применить трюк с созданием пустого объекта и обменом содержимым.
> Для POD-типов и некоторых других вместо обмена можно применить memmove,
> что, собственно, CArray и делает.
>
> Или ты имеешь в виду именно списки?
> Так у std::list есть функция splice, как раз для того, чтобы переносить
> цепочку узлов из одного контейнера в другой, без конструирования-разрушения.

Нет, именно вектора.

Я не понимаю, как можно не понимать глубокой разницы между ПЕРЕМЕЩЕНИЕМ
элемента коллекции из одного хранилища в другое (что вообще-то внутреннее
дело коллекции и нас вообще касаться не должно) и УДАЛЕНИЕМ элемента или
добавлением элемента в коллекцию. Разные же вещи. В STL -- одно и то же.
В MFC-шных коллекциях различается чётко и ясно.
Posted via RSDN NNTP Server 2.1 beta
Re[10]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 29.07.10 20:11
Оценка:
ononim wrote:

> C>Поздравляю, ononim, Вы изобрели std::auto_ptr<T>:

> я знаю

Интересно, а что std::auto_ptr<T> в стандартных контейнерах
хранить нельзя, не знал ?
Posted via RSDN NNTP Server 2.1 beta
Re[11]: Зачем нужен итератор?
От: ononim  
Дата: 29.07.10 20:56
Оценка:
>> C>Поздравляю, ononim, Вы изобрели std::auto_ptr<T>:
>> я знаю
MZ>Интересно, а что std::auto_ptr<T> в стандартных контейнерах
MZ>хранить нельзя, не знал ?
знал
но в некоторых реализациях STL можно
но так делать нельзя, я знаю
Как много веселых ребят, и все делают велосипед...
Re[11]: Зачем нужен итератор?
От: Кодт Россия  
Дата: 29.07.10 21:17
Оценка:
Здравствуйте, MasterZiv, Вы писали:

>> Добавить КУДА? в интерфейс произвольного объекта (рядом с ctor, cctor,

>> dtor) или в интерфейс контейнера?

MZ>Да в стандарт. STL добавили, а


Ещё раз, куда в стандарт. Добавить move constructor, или метод vector::move_element_to_another_vector ?

>> А его в обоих случаях удаляют насовсем.


MZ>Здрасте, приехали.


И снова здравствуйте!
Объект располагается где-то в памяти. Когда мы освобождаем эту память (возвращаем её в кучу, либо оставляем пустой для грядущих добавлений), куда девается объект? Уничтожается.

Это тебе не управляемая среда вроде смолтока, явы или дотнета, где (в недрах менеджера памяти) можно взять и передвинуть кучу байтов с места на место, сохранив целостность ссылок на эти байты и из этих байтов.

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

>> Или ты имеешь в виду именно списки?


MZ>Нет, именно вектора.


MZ>Я не понимаю, как можно не понимать глубокой разницы между ПЕРЕМЕЩЕНИЕМ

MZ>элемента коллекции из одного хранилища в другое (что вообще-то внутреннее
MZ>дело коллекции и нас вообще касаться не должно) и УДАЛЕНИЕМ элемента или
MZ>добавлением элемента в коллекцию. Разные же вещи. В STL -- одно и то же.
MZ>В MFC-шных коллекциях различается чётко и ясно.

Ну хорошо, в MFC/ATL есть у контейнеров метод RelocateElements.
И часто ты им пользуешься?

Вообще-то, он предназначен для внутренних нужд — через него реализованы функции вставки и удаления.
Переопределяя его (а лучше — не его, а трейтс-параметр шаблона CArray) ты можешь добиваться наилучшей и наиправильной работы этих функций. (Ибо, по дефолту там UB на memmove).

В STL перемещение элементов вектора спрятано внутрь и прибито гвоздями. Как именно — забота версии STL.

У Dinkumware (в поставке для VC) — там присваивания. Можно сделать через swap. Можно через копирования.
Для некоторых типов данных эффективнее swap, для других присваивание, для третьих копирование. (Ибо, по дефолту swap делается через копирование и два присваивания).


Вообще, идеология STL такова, что обычные контейнеры необязательно эффективны, но зато универсальны.
Когда возникает желание побороться за производительность, — пиши свой контейнер. Хочешь — делай его STL-like, не хочешь — не делай.
Присобачить STL-like интерфейс к CArray — раз плюнуть.
А с помощью буста можно и трейтсы с правильной реализацией RelocateElements на все случаи жизни (ну, хотя бы проверяя тип на POD и на известное семейство memmove-безопасных).
Перекуём баги на фичи!
Re[8]: Зачем нужен итератор?
От: Юрий Жмеренецкий ICQ 380412032
Дата: 29.07.10 23:23
Оценка:
Здравствуйте, zitz, Вы писали:

ЮЖ>>В случае, например списка, сложноть 'T& operator [] (unsigned n)' будет линейной, поэтому сложность перебора всех элементов с помощью этого интерфейса будет квадратичной.


Z>Я делал себе такую штуку для множества, достаточно ввести m_nNextIndex и последовательный перебор всех значений (а у меня в основном такой) будет иметь обычную сложность.

Z>Эх, люблю я цикл for( int i=0; i<set.GetCount(); i++) set[i];

  Скрытый текст
Z>
Z>UINT CXXXUIntSet::operator []( int nIndex )
Z>{
Z>    ASSERT( nIndex < GetSize() );
Z>    std::set<UINT>* pSet1 = (std::set<UINT>*)m_pData;

Z>    UINT retData = 0;

Z>    if ( m_nNextIndex < 0 )
Z>    {
Z>        *(std::set<UINT>::iterator*)m_pNextPosition = pSet1->begin();
Z>        m_nNextIndex = 0;
Z>    }

Z>    if ( nIndex == m_nNextIndex )
Z>    {
Z>        retData = **(std::set<UINT>::iterator*)m_pNextPosition;
Z>        (*(std::set<UINT>::iterator*)m_pNextPosition)++;
Z>        *(std::set<UINT>::iterator*)m_pNextPosition != pSet1->end() ? m_nNextIndex++ : m_nNextIndex = -1;
Z>    }
Z>    else
Z>    {
Z>        *(std::set<UINT>::iterator*)m_pNextPosition = pSet1->begin();
Z>        m_nNextIndex = 0;
Z>        while ( nIndex >= m_nNextIndex )
Z>        {
Z>            retData = **(std::set<UINT>::iterator*)m_pNextPosition;
Z>            (*(std::set<UINT>::iterator*)m_pNextPosition)++;
Z>            m_nNextIndex++;
Z>        }
Z>    }

Z>    return retData;
Z>}
Z>

В таком решении присутствует нарушение SRP — на контейнер возложено больше обязанностей, чем необходимо, соответственно на ровном месте возникает усложнение контракта контейнера и его реализации. Как следствие — потеря реентерабельности (см. сообщение Кодт'а). Кстати, перечисление в обратном порядке выполняется за O(n^2).
Re[10]: Зачем нужен итератор?
От: night beast СССР  
Дата: 30.07.10 04:28
Оценка:
Здравствуйте, MasterZiv, Вы писали:

MZ>night beast wrote:


>> O> ZuPtr(T *t):_t(t) {}

>> O> ZuPtr(const ZuPtr &z):_t(z._t) {z._t = 0;}
>> O> ZuPtr &operator =(const ZuPtr &z){_t = z._t; z._t = 0; return *this;}
>> O> ~ZuPtr() {delete _t;}
>> O> T* operator ->(){return _t;}

>> в std алгоритмах бывает используются временные переменные, так что твое

>> решение не катит.

MZ>А чем они мешают ... что-то не соображу.


по той-же причине, почему не рекомендуют хранить auto_ptr в стандартных контейнерах:
vector<FooPtr> v;
v.push_back( new Foo () );
{
   FooPtr tmp = v[0]; // захватываем память
} // здесь мы освобождаем память 
v[0]; // здесь имеем v[0] == 0;


MZ>Но могу сказать точно: первое расширение содержимого vesctor-а

MZ>вызовит деструкторы в старом "хранилище" и указатели в новом хранилище
MZ>уже будут невалидные -- все объекты будут удалены.

или я не понял задачу, или одно из двух;
расширение вектора не вызывает деструкторов хранящихся в нем объектов.
итераторы вектора да, становятся не валидными. хочешь обратного, используй list

>> формально, задачу решает наследование от std::vector<FooPtr> и нужного

>> определение деструктора.

MZ>Этого, блин, МАЛО. Нужно ещё заставить различаться ситуацию удаления

MZ>элементов НАСОВСЕМ от переноса их в новое хранилище.
MZ>Я ничего кроме написания собственного аллокатора с состоянием не
MZ>придумал. Решение непереносимо и ужасно.

деструктор это и есть удаление элементов насовсем.
можно более конкретно описать задачу?
Re[11]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 30.07.10 09:02
Оценка:
night beast wrote:

> или я не понял задачу, или одно из двух;

> расширение вектора не вызывает деструкторов хранящихся в нем объектов.
> итераторы вектора да, становятся не валидными. хочешь обратного,
> используй list

Ты уверен ?

> деструктор это и есть удаление элементов насовсем.

> можно более конкретно описать задачу?

Да я уже описал. Хранить в коллекции (ну давай для конкретики вектор)
полиморфные объекты, унаследованные от класса, скажем, CMyFavoriteObject,
по ссылке, без дополнительных накладных расходов по памяти и так,
чтобы коллекция была бы ответственна за удаление этих объектов
(т.е. клиент удалил из коллекции элемент -- он удалился вообще,
т.е. delete).
Posted via RSDN NNTP Server 2.1 beta
Re[12]: Зачем нужен итератор?
От: MasterZiv СССР  
Дата: 30.07.10 09:33
Оценка: +1 -1
Кодт wrote:

> Объект располагается где-то в памяти. Когда мы освобождаем эту память

> (возвращаем её в кучу, либо оставляем пустой для грядущих добавлений),
> куда девается объект? Уничтожается.

Ты по-моему совсем меня не понимаешь. Я совсем о другом.

> Ну хорошо, в MFC/ATL есть у контейнеров метод RelocateElements.

> И часто ты им пользуешься?

Вообще не использую. Но там есть замечательный
глобальный метод типа

void AFXAPI DestructElements( MyClass **pElements, int nCount );

который достаточно определить для твоего хранимого
в контейнере типа и он будет вызываться при удалении
элементов из контейнера.

> Вообще, идеология STL такова, что обычные контейнеры необязательно

> эффективны, но зато универсальны.
> Когда возникает желание побороться за производительность, — пиши свой
> контейнер. Хочешь — делай его STL-like, не хочешь — не делай.

Дело не в производительности, а именно в универсальности. Перемещение
элемента в другое хранилище -- отдельная операция, она НЕ СВЯЗАНА
с удалением объекта вообще никак. Так вот именно она должна
была специфицирована в стандарте и как-то реализована. Но ребята
решили притвориться академиками и не париться. Конструктор-деструктор.
Типа другого пути нет.
Posted via RSDN NNTP Server 2.1 beta
Re[12]: Зачем нужен итератор?
От: night beast СССР  
Дата: 30.07.10 09:40
Оценка:
Здравствуйте, MasterZiv, Вы писали:

>> или я не понял задачу, или одно из двух;

>> расширение вектора не вызывает деструкторов хранящихся в нем объектов.
>> итераторы вектора да, становятся не валидными. хочешь обратного,
>> используй list

MZ>Ты уверен ?


был уверен. посмотрел -- облом
в оправдание могу сказать, что пользуюсь своими поделками

>> деструктор это и есть удаление элементов насовсем.

>> можно более конкретно описать задачу?

MZ>Да я уже описал. Хранить в коллекции (ну давай для конкретики вектор)

MZ>полиморфные объекты, унаследованные от класса, скажем, CMyFavoriteObject,
MZ>по ссылке, без дополнительных накладных расходов по памяти и так,
MZ>чтобы коллекция была бы ответственна за удаление этих объектов
MZ>(т.е. клиент удалил из коллекции элемент -- он удалился вообще,
MZ>т.е. delete).

boost::ptr_vector (уже было озвучено) не подходит?
Re[13]: Зачем нужен итератор?
От: Кодт Россия  
Дата: 30.07.10 09:54
Оценка:
Здравствуйте, MasterZiv, Вы писали:

MZ>Дело не в производительности, а именно в универсальности. Перемещение

MZ>элемента в другое хранилище -- отдельная операция, она НЕ СВЯЗАНА
MZ>с удалением объекта вообще никак. Так вот именно она должна
MZ>была специфицирована в стандарте и как-то реализована. Но ребята
MZ>решили притвориться академиками и не париться. Конструктор-деструктор.
MZ>Типа другого пути нет.

Ты, видимо, упорно путаешь CArray и его производные — CPtrArray, CObArray.
Их элементы — указатели. Понимаешь, указатели, а не указуемые значения.

Если тебе хочется, чтобы контейнер выполнял глубокое копирование и глубокое удаление (указателей вместе с указуемыми) — пожалуйста, boost::ptr_vector.
Или vector<shared_ptr>, если не хочешь иметь разные траблы с монопольным владением.
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.