LINUX.ORG.RU

Контейнеры в C


2

3

Будучи сиплюсплюсником, меня давно интересует как те же задачи выполняются в C. Знаю, на ЛОРе есть множество приверженцев C, надеюсь они ответят на пару простых вопросов.

Я являюсь активным сторонником идеи «Алгоритм должен работать настолько быстро, насколько позволяет железо». С этой точки зрения C++ даёт потрясающие возможности из-за своей системы кодогенерации. Да, я имею в виду шаблоны.

Не будем зарываться в дебри boost'а, возьмём простую задачу - контейнеры.

Для примера я взял односвязные списки в GTK GSList и Qt QList. QList позволяет хранить как простые, так и сложные типы с конструкторами, деструкторами, типы с общими данными. При этом накладных расходов на выделение в куче не происходит, а при реаллокации выбирается нужный алгоритм в зависимости от QTypeInfo<T>, к примеру для int будет вызван memcpy(), а для QString оператор копирования. Кроме того блок данных заранее резервируется и для сложных типов вызывается placement constructor. Для удаления данных по указателям используется алгоритм qDeleteAll(from,to), который сам дёрнет нужные конструкторы. Беглый просмотр GSList показал, что в нём можно хранить только указатели, со всеми вытекающими накладными расходами на аллокацию/уничтожение памяти, ручное кастирование, слежение за утечками, фрагментацию памяти.

Аналогичная ситуация со связными списками GList и QLinkedList.

Это даже не затрагивая вопрос о типизации, в C++ компилятор сразу даст по рукам при попытке записать в контейнер неверный тип или с помощью неявного преобразования с explicit-конструктором.

Далее, счётчики ссылок и деструкторы.

К примеру, в C++ список строк будет выглядеть как QList<QString>, при этом можно забыть про внутреннюю структуру строки - конструктор, деструктор и операторы копирования сами занимаются подсчётом ссылок, разделением и уничтожением данных. Вернуть строку из функции проще простого:

QString foo()
{
    return listOfStrings.at( 5 );
}

Этот код абсолютно безопасен и оптимален с точки зрения использования памяти и процессора, поскольку время тратится только на увеличение счётчика и копирование указателя.

В GTK я сходу нашёл GString:

struct GString 
{
  gchar *str;
  gint len;
};

Причём экземпляры этой структуры выделяются в куче. Никаких счётчиков ссылок не видно, значит при обычном присваивании нужно или руками следить за всеми указателями на строку, или каждый раз делать глубокое копирование, опять же тратя и фрагментируя память и ресурсы процессора.

Собственно, мне интересно каков принцип работы сишников в таких простых задачах. Наплевать на производительность, выделять всё в куче, делать глубокое копирование сложных структур, всегда следить за кастированием к нужному типу, писать для любой структуры деструктор и следить за его вызовом?

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

★★★★★
Ответ на: комментарий от shty

>так что показываю на пальцах общую технологию работы

Тю! Оператор присваивания лучше сделай. Не левую функцию, а именно оператор, чтобы можно было писать str1 = str2.

linuxfan
()
Ответ на: комментарий от linuxfan

Не левую функцию, а именно оператор, чтобы можно было писать str1 = str2.


пишется обертка над custom_string у которой одно поле типа custom_string + операторы:
- operator=( const string& str ), чтоб можно было присвоить значение от обычной строки
- operator char*(), чтоб можно было присвоить значение для обычной строки

ну и прописать еще методы все + другие стандартные операторы, сделать все inline - и по скорости и памяти проигрыша не будет, но массу лишней работы сделать надо будет - да

// ahonimus

anonymous
()
Ответ на: комментарий от linuxfan

> Тю!

Ты еще не угомонился?))) Ты уже исследовал вопрос как хранятся строки в Си++? Готов рассказать нам разницу в подходах с хранением строк в Си и Си++ ?)))

shelA
()
Ответ на: комментарий от linuxfan

так что показываю на пальцах общую технологию работы

Тю! Оператор присваивания лучше сделай. Не левую функцию, а именно оператор, чтобы можно было писать str1 = str2.

ну так и сделай, я тебе показал технологию :)

shty ★★★★★
()
Ответ на: комментарий от shty

>ну так и сделай, я тебе показал технологию :)

Ояфигеюстакихумников! Написал #include <string> = показал технологию? :D Или ты думаешь, я без твоей божественной просвещенности не знаю о c_str()?

linuxfan
()
Ответ на: комментарий от shelA

>Ты уже исследовал вопрос как хранятся строки в Си++?

В отличии от тебя, я давно и прекрасно знаю, чем std::string отличается от char*. Хотя бы тем, что в ней могут встречаться нулевые байты. Также в целях совместимости в конце строки всегда стоит завершающий нулевой байт.

linuxfan
()
Ответ на: комментарий от linuxfan

ну так и сделай, я тебе показал технологию :)

Ояфигеюстакихумников! Написал #include <string> = показал технологию? :D Или ты думаешь, я без твоей божественной просвещенности не знаю о c_str()?

а, так ты не распарсил пятистрочник без каментов? :) ну что я могу сказать - иди изучай C++ и внимательно думай про концепцию итераторов

ЗЫ если с C++ не получится - свисти, книжку подкину :)

shty ★★★★★
()
Ответ на: комментарий от linuxfan

ты думаешь, я без твоей божественной просвещенности не знаю о c_str()?

сначала не заметил, не потрудитесь объяснить причём тут c_str()?

shty ★★★★★
()
Ответ на: комментарий от shty

>а, так ты не распарсил пятистрочник без каментов? :)

Я тебе предложил попробовать написать строку с альтернативным аллокатором, которую можно прозрачно использоваться рядом с обычной std::string. Ты мне в ответ разродился функцией append, да еще приплел туда std::copy + back_inserter (опять-таки к вопросу твоего (не)понимания, во что это развернется). Очевидно, что ты просто не понимаешь, насколько геморно наследоваться от std::basic_string для добавления парочки операторов.

ЗЫ если с C++ не получится - свисти, книжку подкину :)

Оно и видно, что весь твой «опыт» основывается на книжке «C++ для самых маленьких».

linuxfan
()
Ответ на: комментарий от shty

>сначала не заметил, не потрудитесь объяснить причём тут c_str()?

Какой ты однако странный: c_str() дает некий общий знаменатель, с помощью которого можно скрестить строки с различными аллокаторами: сравнение, присваивание, конкатенация — все это реализуется через подстановку other_string.c_str() вместо other_string в нужных местах.

linuxfan
()
Ответ на: комментарий от linuxfan

>а, так ты не распарсил пятистрочник без каментов? :)

Я тебе предложил попробовать написать строку с альтернативным аллокатором, которую можно прозрачно использоваться рядом с обычной std::string.

и? что ты не увидел в приведённом примере?

Ты мне в ответ разродился функцией append, да еще приплел туда std::copy + back_inserter (опять-таки к вопросу твоего (не)понимания, во что это развернется). Очевидно, что ты просто не понимаешь, насколько геморно наследоваться от std::basic_string для добавления парочки операторов.

доооо, конечно, а до этого ты говорил что очень трудно реализовать append для строк с разным аллокатором :)

иди поработай мальчик, наберись опыта, может перестанешь чушь пороть :)

shty ★★★★★
()
Ответ на: комментарий от linuxfan

сначала не заметил, не потрудитесь объяснить причём тут c_str()?

Какой ты однако странный: c_str() дает некий общий знаменатель, с помощью которого можно скрестить строки с различными аллокаторами: сравнение, присваивание, конкатенация — все это реализуется через подстановку other_string.c_str() вместо other_string в нужных местах.

больше никак нельзя, да? :))))

и да, что Вы думаете про подводные камни такого «общего знаменателя»?

shty ★★★★★
()
Ответ на: комментарий от shty

>и? что ты не увидел в приведённом примере?

Я тебе уже не первый раз русским по белому пишу: я не увидел operator=, который, как известно, может быть только членом класса.

доооо, конечно, а до этого ты говорил что очень трудно реализовать append для строк с разным аллокатором :)

Читай то, что я пишу, а не то, что тебе кажется: я хочу от тебя operator+ и осознание того, сколько вариантов operator+ понадобится для того, чтобы, скажем, свободно оперировать строками с тремя разными типами аллокаторов.

иди поработай мальчик, наберись опыта, может перестанешь чушь пороть :)

Кстати, а ты правда настолько «умен», что не понимаешь, что твой append — говно?

linuxfan
()
Ответ на: комментарий от shty

>и да, что Вы думаете про подводные камни такого «общего знаменателя»?

Отнаследуйся уже от basic_string, чтобы открыть новый для себя факт, о котором, видимо, в «C++ для самых маленьких» не писали: тебе нужно продублировать в каждом наследнике все конструкторы родительстких классов для того, чтобы это было похоже на std::string для тех, кто его использует. Но при этом все равно твоя строка со свистом пролетит, если внешней библиотеке нужен будет std::string. Тогда ты будешь везде ручками писать mystring.c_str(), что приведет к созданию временного объекта, но об этом ведь в «C++ для самых маленьких» тоже не пишут?

linuxfan
()
Ответ на: комментарий от linuxfan

и? что ты не увидел в приведённом примере?

Я тебе уже не первый раз русским по белому пишу: я не увидел operator=, который, как известно, может быть только членом класса.

да ты вообще много пишешь, только читать не умеешь :)

>доооо, конечно, а до этого ты говорил что очень трудно реализовать append для строк с разным аллокатором :)

Читай то, что я пишу, а не то, что тебе кажется: я хочу от тебя operator+ и осознание того, сколько вариантов operator+ понадобится для того, чтобы, скажем, свободно оперировать строками с тремя разными типами аллокаторов.

:))) ты код вообще смотрел? охлол, день школоты на лоре

>иди поработай мальчик, наберись опыта, может перестанешь чушь пороть :)

Кстати, а ты правда настолько «умен», что не понимаешь, что твой append — говно?

ну-тко, просвети тёмных людишек :)

shty ★★★★★
()
Ответ на: комментарий от shty

>ну-тко, просвети тёмных людишек :)

1. Напарква там std::string*? В твоей книжке не пишут о ссылках?

2. Почему тип возвращаемого значения void? Хотя ты все равно не осилишь догадаться, почему там нужен не void.

3. Что я получу, если напишу append(&str, «abc»)? append(&str, 123)?

ты код вообще смотрел?

Я бы такое говно постыдился кодом называть.

linuxfan
()
Ответ на: комментарий от linuxfan

>> Ты уже исследовал вопрос как хранятся строки в Си++?

В отличии от тебя, я давно и прекрасно знаю, чем std::string отличается от char*. Хотя бы тем, что в ней могут встречаться нулевые байты. Также в целях совместимости в конце строки всегда стоит завершающий нулевой байт.

Ну расскажи тогда нам тупым плюсовикам, что вернет нам функция string::c_str(), если «в ней могут встречаться нулевые байты»?)))

ЗЫ: Кинь ссылочку где ты этого бреда начитался. Или походу сам догадался?)))

shelA
()
Ответ на: комментарий от linuxfan

> тебе нужно продублировать в каждом наследнике все конструкторы родительстких классов

видимо ты не увидел - там еще деструктор невиртуальный

// ahonimus

anonymous
()
Ответ на: комментарий от shelA

>Ну расскажи тогда нам тупым плюсовикам, что вернет нам функция string::c_str(), если «в ней могут встречаться нулевые байты»?)))

Ты получил перегрев мозга из-за факта того, что в std::string могут быть нулевые байты?

linuxfan
()
Ответ на: комментарий от linuxfan

и да, что Вы думаете про подводные камни такого «общего знаменателя»?

Отнаследуйся уже от basic_string, чтобы [..]

посмотри уже наконец код stl и осознай что твоя фраза «Отнаследуйся уже от basic_string» говорит о том что ты в вопросе разбираешься слабо :)

shty ★★★★★
()
Ответ на: комментарий от anonymous

>видимо ты не увидел - там еще деструктор невиртуальный

Это как раз не проблема: ты же не собираешься напихивать в наследника виртуальных функций и полей данных, требущих деинициализации.

linuxfan
()
Ответ на: комментарий от shty

>посмотри уже наконец код stl и осознай что твоя фраза «Отнаследуйся уже от basic_string» говорит о том что ты в вопросе разбираешься слабо :)

Еще раз для тупых: что нужно сделать, чтобы откомпилировался код my_string = std_string, где my_string = std_string. Ах, да: почему не получится сделать обратное std_string = my_string?

linuxfan
()
Ответ на: комментарий от linuxfan

>> Ну расскажи тогда нам тупым плюсовикам, что вернет нам функция string::c_str(), если «в ней могут встречаться нулевые байты»?)))

Ты получил перегрев мозга из-за факта того, что в std::string могут быть нулевые байты?

Ты забыл ссылочку кинуть? ПРУФ - знаешь, что такое?

shelA
()
Ответ на: комментарий от linuxfan

ну-тко, просвети тёмных людишек :)

1. Напарква там std::string*? В твоей книжке не пишут о ссылках?

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

а что сынку, большая разница между ссылкой и указателем в данном случае?

2. Почему тип возвращаемого значения void? Хотя ты все равно не осилишь догадаться, почему там нужен не void.

потому что я так захотел, а ты не сумел ничего вразумительного по теме сказать :)

3. Что я получу, если напишу append(&str, «abc»)? append(&str, 123)?

по бащке от компилятора получишь, потому что ССЗБ :)

shty ★★★★★
()
Ответ на: комментарий от shelA

>Ты забыл ссылочку кинуть? ПРУФ - знаешь, что такое?

А своим умишком не получается?

#include <string>
#include <iostream>

int main()
{
	std::string s(5, 0);
	s += "a";
	std::cout << s.length() << ": \"" << s << "\"\n";
	return 0;
}
linuxfan
()
Ответ на: комментарий от shty

>а что сынку, большая разница между ссылкой и указателем в данном случае?

Огромная. append(NULL, ...).

потому что я так захотел, а ты не сумел ничего вразумительного по теме сказать :)

Теперь представь на секунду, что операторы =, += и прочие возвращают не object&, а void. А еще я могу писать std_string.append(«1»).append(«2»), а твоим убогим костылем такого нельзя сделать.

по бащке от компилятора получишь, потому что ССЗБ :)

И сообщение об ошибке будет маловразумительным, но жутко многословным.

linuxfan
()
Ответ на: комментарий от linuxfan

посмотри уже наконец код stl и осознай что твоя фраза «Отнаследуйся уже от basic_string» говорит о том что ты в вопросе разбираешься слабо :)

Еще раз для тупых: что нужно сделать, чтобы откомпилировался код my_string = std_string, где my_string = std_string. Ах, да: почему не получится сделать обратное std_string = my_string?

эхехе :) опять ты за старое, повторю для слоупоков: читай про концепцию итераторов

shty ★★★★★
()
Ответ на: комментарий от shty

>эхехе :) опять ты за старое, повторю для слоупоков: читай про концепцию итераторов

Дело тут не в итераторах, а в создании соответствующих operator=. Ты точно не нюхал боевой плюсовки.

linuxfan
()
Ответ на: комментарий от linuxfan

а что сынку, большая разница между ссылкой и указателем в данном случае?

Огромная. append(NULL, ...).

я ж тебе говорил что это всего лишь пример на коленке, а не готовый код для production, забыл? :)

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

Теперь представь на секунду, что операторы =, += и прочие возвращают не object&, а void. А еще я могу писать std_string.append(«1»).append(«2»), а твоим убогим костылем такого нельзя сделать.

ты хоть один оператор или член класса там видишь? :)

>по бащке от компилятора получишь, потому что ССЗБ :)

И сообщение об ошибке будет маловразумительным, но жутко многословным.

лови выхлоп, кросавчег

1>------ Build started: Project: test, Configuration: Release Win32 ------1>  main.cpp
1>main.cpp(33): error C2228: left of '.begin' must have class/struct/union
1>          type is 'const int'
1>          main.cpp(50) : see reference to function template instantiation 'void append_to_string<int>(std::string *,const _ty &)' being compiled
1>          with
1>          [
1>              _ty=int
1>          ]
1>main.cpp(33): error C2228: left of '.end' must have class/struct/union
1>          type is 'const int'
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
shty ★★★★★
()
Ответ на: комментарий от linuxfan

>эхехе :) опять ты за старое, повторю для слоупоков: читай про концепцию итераторов

Дело тут не в итераторах, а в создании соответствующих operator=. Ты точно не нюхал боевой плюсовки.

это ты просто плюсы с шаблонами нюхал очень издалека, никто в таких случаях не пользуется operator=, пользуются итераторами и обобщёнными алгоритмами, посмотри что написано в <algorithm>

shty ★★★★★
()
Ответ на: комментарий от shty

>пользуются итераторами и обобщёнными алгоритмами

Годно только для того, чтобы подрочить в холиварах. IRL жуткое тормозилово.

linuxfan
()
Ответ на: комментарий от linuxfan

пользуются итераторами и обобщёнными алгоритмами

Годно только для того, чтобы подрочить в холиварах. IRL жуткое тормозилово.

о, ещё один фанат c-style оптимизации :) std::copy тоже жуткое тормозилово? :)

shty ★★★★★
()
Ответ на: комментарий от shty

>о, ещё один фанат c-style оптимизации :) std::copy тоже жуткое тормозилово? :)

справедливости ради замечу, что back_inserter действительно жуткий тормоз. Exception safety, мать её.

legolegs ★★★★★
()
Ответ на: комментарий от legolegs

о, ещё один фанат c-style оптимизации :) std::copy тоже жуткое тормозилово? :)

справедливости ради замечу, что back_inserter действительно жуткий тормоз. Exception safety, мать её.

ну, во-первых там всё равно для plain old data что-то типа такого лежит

template<class _InIt,
	class _OutIt> inline
	_OutIt _Copy_impl(_InIt _First, _InIt _Last,
		_OutIt _Dest, _Scalar_ptr_iterator_tag)
	{	// copy [_First, _Last) to [_Dest, ...), pointers to scalars
	ptrdiff_t _Count = _Last - _First;
	_CSTD memmove(&*_Dest, &*_First,
		_Count * sizeof (*_First));
	return (_Dest + _Count);
	}

и std::back_inserter - это чтобы совсем не париться, можно же ручками сделать resize и скопировать range

shty ★★★★★
()
Ответ на: комментарий от linuxfan

Мда, заел ты меня зараза. Хотя я и понимал куда ты клонишь и просил тебя рассказать, а как же при этом поведет себя c_str() ))). Согласен дядюшка Степан, хреново продумал эту тему. Ну не видел чел видимо разницы между байтовым массивом и строкой.))) Видимо действительно рыбой отравился.))) Один ноль в твою пользу)))

shelA
()
Ответ на: комментарий от shty

>std::copy тоже жуткое тормозилово?

Зависит от употребления. Если это std::copy(const char*, const char*, char*), то нормально. Я, правда, все равно не уверен, что gcc может разворачивать его как memcpy.

linuxfan
()
Ответ на: комментарий от linuxfan

std::copy тоже жуткое тормозилово?

Зависит от употребления. Если это std::copy(const char*, const char*, char*), то нормально.

ну да, в случае POD - всё тип топ, а если не POD то и массив Вам не поможет - нельзя их копировать через memmove/memcopy

Я, правда, все равно не уверен, что gcc может разворачивать его как memcpy.

ну это как раз легко проверить, не правда ли? :)

  template<bool _IsMove>
    struct __copy_move<_IsMove, true, random_access_iterator_tag>
    {
      template<typename _Tp>
        static _Tp*
        __copy_m(const _Tp* __first, const _Tp* __last, _Tp* __result)
        {
	  __builtin_memmove(__result, __first,
			    sizeof(_Tp) * (__last - __first));
	  return __result + (__last - __first);
	}
    };
shty ★★★★★
()
Ответ на: комментарий от Reset

> В нормальном C++ коде указатели за которыми надо следить встречаются очень редко.

Я не понимаю, каким вы образом тогда создаете объекты? Или вы пользуетесь исключительно локальными объектами? Можно пример такого кода, у?

trimm
()
Ответ на: комментарий от Reset

Я весьма редко пользуюсь С++, в основном С/Objective-C. Вас не затруднит объяснить, в чем же профит? Просто, для самообразования мне.

Насколько я знаю, тот же auto_ptr гарантирует, что объект будет удален, когда выйдет из зоны видимости. Т.е., та же локальная переменная по сути? Или я не прав?

Только в том, что вы не забудете поставить в конце delete? И чем это лучше локальных переменных тогда?

trimm
()
Ответ на: комментарий от trimm

Насколько я знаю, тот же auto_ptr гарантирует, что объект будет удален, когда выйдет из зоны видимости. Т.е., та же локальная переменная по сути? Или я не прав?

Верно

Только в том, что вы не забудете поставить в конце delete?

Концов может быть несколько в том числе и неявные, если бросается исключение.

И чем это лучше локальных переменных тогда?

Ничем не лучше не хуже. Только надо учитывать, что указатель может прийти из фабрики и поэтому локальной переменной в этом случае не обойдешься.

Reset ★★★★★
()
Ответ на: комментарий от namezys

А чем он вам не нравится?

Избыточным синтаксис.

Dudraug ★★★★★
()
Ответ на: комментарий от annulen

отсутствие потокового i/o - один из главных недостатков С в моих глазах

Пример кода, для i/o, который в Си++ (с использованием stream'ов) будет более очевидным и функциональным, чем обычный сишный код.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

Пример 1

int i;
double d;
char s[80];

ifstream fin(«file.name»);
fin >> i >> d >> s;
// ...
ofstream fout(«file2»);
fout << s << i << d;


Пример 2 (убийственный)

fin >> MyObject;
fout << MyObject


Конечно, если тебе проще запоминать управляющие символы scanf, чем направление стрелок, то на вкус и цвет...

annulen ★★★★★
()
Ответ на: комментарий от annulen

Конечно, если тебе проще запоминать управляющие символы scanf, чем направление стрелок, то на вкус и цвет...

Направление стрелок лишь показывают направление, а не тип, лол.

Управляющие символы нужны для форматного ввода вывода, в Си++ мне еще кучу чуши придется для этого знать.

Пример 2 (убийственный)

fin >> MyObject;
fout << MyObject

А выравнивание?

int i;
double d;
char s[80];

ifstream fin("file.name");
fin >> i >> d >> s;
// ...
ofstream fout("file2");
fout << s << i << d;

Ну во-первых:

int i;
double d;
char s[80];

std::ifstream fin("file.name");
std::fin >> i >> d >> s;
// ...
std::ofstream fout("file2");
std::fout << s << i << d;

Во-вторых: ты еще умышленно пропустил std::endl; В-третьих: еще могут встречаться моды открытия файла std::ios_base::ate например. В-четвертых вот код на сишке

int i;
double d;
char s[80];

FILE *fin;
fin = fopen("file.name", "rt");

fscanf(fin,"%s%lf%i", s, &d, &i);
// ...
FILE *fout;
fout = fopen ("file2");

fprintf(fout, "%s %d %i\r\n", s, d, i);

По мне так в си намного приятние выглядит это все.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

>Во-вторых: ты еще умышленно пропустил std::endl

\n тоже прокатит. std:: повсюду пихать - идиотизм (разве что в заголовках, чтобы не инклюдить лишнего), нормальные люди используют using

fprintf(fout, «%s %d %i\r\n», s, d, i);

во-первых, для double вместо %d нужно %f - первый подводный камень. во-вторых, лично мне совсем не нравится необходимость дублирования списка переменных (сначала типы, потом данные) - в потоке имеем все и сразу. да, я знаю что красивое форматирование даблов в printf делать удобнее. scanf вообще уродлив, имхо

В-третьих: еще могут встречаться моды открытия файла std::ios_base::ate например.

могут, но посмотреть название ios-мода ничуть не дольше, чем FILE-мода. Да и std:: опять не нужен

annulen ★★★★★
()
Ответ на: комментарий от annulen

\n тоже прокатит. std:: повсюду пихать - идиотизм (разве что в заголовках, чтобы не инклюдить лишнего), нормальные люди используют using

using для std - бред.

Dudraug ★★★★★
()
Ответ на: комментарий от annulen

во-первых, для double вместо %d нужно %f

Знаю, опечатка. Лучше еще вообще %lf.

Dudraug ★★★★★
()
Ответ на: комментарий от Dudraug

>using для std - бред.

using namespace std? возможно (но используется крайне часто). А using std::cout - имхо вполне разумно

annulen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.