Russian Qt Forum
Май 10, 2024, 04:44 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 ... 8   Вниз
  Печать  
Автор Тема: Частный случай механизма сигнал-слот  (Прочитано 72040 раз)
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« : Февраль 17, 2011, 17:35 »

Решил тут для себя разобраться в реализации сигнально-слотового механизма, основаного на шаблонах.
Выкладываю то что успел накатать (описание ниже):

файл signal_slot.h
Код
C++ (Qt)
#ifndef SIGNAL_SLOT_H
#define SIGNAL_SLOT_H
 
#include <list>
 
template <class T_receiver, class T_return, class T_arg>
class Slot
{
public:
   typedef T_return (T_receiver::*function_type_1)(T_arg);
   typedef T_return (T_receiver::*function_type_2)(const T_arg &);
   typedef T_return (T_receiver::*function_type_3)(T_arg) const;
   typedef T_return (T_receiver::*function_type_4)(const T_arg &) const;
 
 
   Slot(T_receiver *obj, function_type_1 slot);
   Slot(T_receiver *obj, function_type_2 slot);
   Slot(T_receiver *obj, function_type_3 slot);
   Slot(T_receiver *obj, function_type_4 slot);
 
   ~Slot();
 
   T_return operator() (const T_arg &arg);
 
private:
   Slot();
   function_type_1 _slot_1;
   function_type_2 _slot_2;
   function_type_3 _slot_3;
   function_type_4 _slot_4;
 
   T_receiver *_obj;
   int _index;
};
 
 
template <class T_receiver, class T_return, class T_arg>
inline Slot<T_receiver, T_return, T_arg>::Slot()
{
}
 
template <class T_receiver, class T_return, class T_arg>
inline Slot<T_receiver, T_return, T_arg>::Slot(T_receiver *obj, function_type_1 slot)
{
   _obj = obj;
   _slot_1 = slot;
   _index = 1;
}
 
template <class T_receiver, class T_return, class T_arg>
inline Slot<T_receiver, T_return, T_arg>::Slot(T_receiver *obj, function_type_2 slot)
{
   _obj = obj;
   _slot_2 = slot;
   _index = 2;
}
 
template <class T_receiver, class T_return, class T_arg>
inline Slot<T_receiver, T_return, T_arg>::Slot(T_receiver *obj, function_type_3 slot)
{
   _obj = obj;
   _slot_3 = slot;
   _index = 3;
}
 
template <class T_receiver, class T_return, class T_arg>
inline Slot<T_receiver, T_return, T_arg>::Slot(T_receiver *obj, function_type_4 slot)
{
   _obj = obj;
   _slot_4 = slot;
   _index = 4;
}
 
template <class T_receiver, class T_return, class T_arg>
inline T_return Slot<T_receiver, T_return, T_arg>::operator ()(const T_arg &arg)
{
   if (_index == 1)
      return (_obj->*_slot_1)(arg);
   if (_index == 2)
       return (_obj->*_slot_2)(arg);
   if (_index == 3)
       return (_obj->*_slot_3)(arg);
 
   return (_obj->*_slot_4)(arg);
}
 
 
template <class T_receiver, class T_return, class T_arg>
class Signal
{
public:
   typedef T_return (T_receiver::*function_type_1)(T_arg);
   typedef T_return (T_receiver::*function_type_2)(const T_arg &);
   typedef T_return (T_receiver::*function_type_3)(T_arg) const;
   typedef T_return (T_receiver::*function_type_4)(const T_arg &) const;
 
   Signal();
   ~Signal();
 
   void operator()(const T_arg &arg);
 
   void connect(T_receiver &obj, function_type_1 slot);
   void connect(T_receiver &obj, function_type_2 slot);
   void connect(T_receiver &obj, function_type_3 slot);
   void connect(T_receiver &obj, function_type_4 slot);
 
private:
   std::list<Slot<T_receiver, T_return, T_arg>* > _list;
   typedef typename std::list<Slot<T_receiver, T_return, T_arg>* >::iterator _iterator;
 
};
 
template <class T_receiver, class T_return, class T_arg>
inline Signal<T_receiver, T_return, T_arg>::Signal()
{
}
 
template <class T_receiver, class T_return, class T_arg>
inline Signal<T_receiver, T_return, T_arg>::~Signal()
{
   _list.clear();
}
 
template <class T_receiver, class T_return, class T_arg>
inline void Signal<T_receiver, T_return, T_arg>::connect(T_receiver &obj, function_type_1 slot)
{
   _list.push_back(new Slot<T_receiver, T_return, T_arg>(&obj, slot));
}
 
template <class T_receiver, class T_return, class T_arg>
inline void Signal<T_receiver, T_return, T_arg>::connect(T_receiver &obj, function_type_2 slot)
{
   _list.push_back(new Slot<T_receiver, T_return, T_arg>(&obj, slot));
}
 
template <class T_receiver, class T_return, class T_arg>
inline void Signal<T_receiver, T_return, T_arg>::connect(T_receiver &obj, function_type_3 slot)
{
   _list.push_back(new Slot<T_receiver, T_return, T_arg>(&obj, slot));
}
 
template <class T_receiver, class T_return, class T_arg>
inline void Signal<T_receiver, T_return, T_arg>::connect(T_receiver &obj, function_type_4 slot)
{
   _list.push_back(new Slot<T_receiver, T_return, T_arg>(&obj, slot));
}
 
template <class T_receiver, class T_return, class T_arg>
inline void Signal<T_receiver, T_return, T_arg>::operator ()(const T_arg &arg)
{
   for (_iterator it = _list.begin(); it != _list.end(); it++)
       (*it)->operator()(arg);
}
 
#endif // SIGNAL_SLOT_H
 


Краткое описание.

Во-первых Сигнал имеет только один передаваемый аргумент (я не думаю что это сильно ущербно, поскольку если требуется передать более одного аргумента, всегда можно создать структуру или класс и объект его уже передавать)
Соответственно слот, также должен иметь только один аргумент.
Далее

Класса Slot
Он фактически представляет из себя функторный класс, который биндит указатель на метод класса, который (метод) будет реализовывать слот.
Этот класс пользователь в своём коде явно нигде не использует. Он введён для удобства и используется классом Signal

Класс Signal объявлен как
Код
C++ (Qt)
template <class T_receiver, class T_return, class T_arg> class Signal
 
где
T_receiver - это класс получатель
T_return - тип возвращаемого аргумента функции (слота)
T_arg - тип аргумента (слота и сигнала, соответственно)

Используется это так:

файл main.cpp
Код
C++ (Qt)
#include <iostream>
#include <string>
 
#include "signal_slot.h"
 
 
template <class T>
class Sender
{
public:
   Sender() {}
   Signal<T, void, std::string> signal;
 
   void run() {
       signal("Hello word!");
   }
};
 
class Receiver
{
public:
   Receiver(){}
 
   void mySlot1(const std::string &str) {
       std::cout << "mySlot1 received a signal: " << str << std::endl;
   }
   void mySlot2(std::string str) {
       std::cout << "mySlot2 receiver a signal: " << str << std::endl;
   }
 
};
 
 
int main()
{
   Sender<Receiver> sender;
   Receiver receiver;
 
   sender.signal.connect(receiver, &Receiver::mySlot1);
   sender.signal.connect(receiver, &Receiver::mySlot2);
 
   sender.run();
 
   return 0;
}
 

Недостатком такого подхода является то, что общатся между собой могут лишь объекты тех классов, что указаны (в параметрах шаблона) при их создании. Т.е. в данном случае, объекты класса Receiver могут получать сигналы от объектов класса Sender. Но объекты класса Sender не могут слать сигналы объектам другого класса, если при определении класа Sender не указать дополнительный параметр шаблона и соответствующий сигнал с этим параметром.

Я пока не раскурил, как эта проблема решена, например в boost или в libsigc++  Грустный
Если кто подскажет, скажу спасибо)

Далее хотелось бы реализовать метод disconnect, а также при коннекте проверять, есть ли в списке зарегистрированных слотов тот, который коннектится. Сейчас, если дважды подрят вызвать connect то в список будет содержать два одинаковых слота, что не есть гут.
Как это сделать пока думаю над этим..

Ещё такой вопрос: кто знает как присвоить указатель на метод одного класса к указателю на метод другого класса? И хорошо ли это?

В общем делаю это,  в первую очередь, чтоб разобраться со всем этим делом))   

архив с проектом прикреплён   


Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #1 : Февраль 17, 2011, 17:43 »

А чем boost::signal не устроил?
Записан

Qt 5.11/4.8.7 (X11/Win)
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #2 : Февраль 17, 2011, 17:50 »

А чем boost::signal не устроил?
Вопрос носит чисто теоретический характер)
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
brankovic
Гость
« Ответ #3 : Февраль 18, 2011, 00:59 »

Недостатком такого подхода является то, что общатся между собой могут лишь объекты тех классов, что указаны (в параметрах шаблона) при их создании.
 
Ещё такой вопрос: кто знает как присвоить указатель на метод одного класса к указателю на метод другого класса? И хорошо ли это?

нет, это очень плохо, в частности из-за гемороя с указателями на виртуальные методы. Но есть другой путь. Можно хранить связанную пару объект-метод в виде boost::function. При этом boost::function требует только возвращаемый тип и типы аргументов, но не говорит какие классы сбайнженные с методами можно в него положить. Собственно библиотека сигналов это boost::function + накрутки. Как написать boost::function это отдельный вопрос. Накрутки это на первый взгляд не страшно, но на самом деле в них вся сложность. На тему можно почитать rationale в документации boost::signal, там очень наглядно разобрано что, как и зачем. После прочтения мне, например, расхотелось самому углубляться Улыбающийся.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #4 : Февраль 18, 2011, 14:16 »

Цитировать
нет, это очень плохо, в частности из-за гемороя с указателями на виртуальные методы.
Ну вот, так и знал))

Ладно, будем думать дальше)
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
brankovic
Гость
« Ответ #5 : Февраль 18, 2011, 14:21 »

Цитировать
нет, это очень плохо, в частности из-за гемороя с указателями на виртуальные методы.
Ну вот, так и знал))

Ладно, будем думать дальше)

boost::function
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #6 : Февраль 18, 2011, 17:14 »

Цитировать
нет, это очень плохо, в частности из-за гемороя с указателями на виртуальные методы.
Ну вот, так и знал))

Ладно, будем думать дальше)

boost::function

Спасибо)) Я кажысь раскурил как это всё можно реализовать)

Сейчас накатаю код, выложу
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #7 : Февраль 18, 2011, 19:28 »

Вроде всё работает  Улыбающийся

Теперь всё хорошо и работает так:

main.cpp
Код
C++ (Qt)
#include <iostream>
#include <string>
#include "signal_slot.h"
 
class A
{
public:
   A() {}
   signal<void, std::string> my_signal;
 
   void run() {
       my_signal("Hello Word!");
   }
};
 
class B
{
public:
   B() {}
 
   void slot1(const std::string &str) {
       std::cout << "slot1, signal - " << str << std::endl;
   }
   void slot2(std::string str) const {
       std::cout << "slot2, signal - " << str << std::endl;
   }
};
 
int main()
{
 
   A a;
   B b;
 
   connect(&b, &B::slot1, &a.my_signal);
   connect(&b, &B::slot2, &a.my_signal);
 
   a.run();
 
   return 0;
}
 
Теперь всё как в Qt и только с использованием шаблонов  Крутой

Функция connect создаёт соединение между слотом объекта класса b и сигналом объекта a.
Сам сигнал представляет из седя шаблонный класс
template <class T_return, class T_arg> class signal
где
T_return - тип возвращаемого аргумента
T_arg - тип передаваемого аргумента

Если нужно чтобы ваш клас умел испускать сигналы необходимо просто включить класс сигнала в определение класса, как в примере:
Код
C++ (Qt)
class A
{
public:
   A() {}
/*
Объявляем сигнал, который будет соеденён с функцией, которая возвращает void
и принимает один аргумент std::string
*/

   signal<void, std::string> my_signal;  
 
/*
В той функции, где необходимо вызвать сигнал, просто вызываем сигнал как обычную функцию  
*/
                                   
   void run() {
       my_signal("Hello Word!");
   }
};
 


Сама реализация механизма сигнал-слот:
Код
C++ (Qt)
#ifndef SIGNAL_SLOT_H
#define SIGNAL_SLOT_H
 
#include <list>
 
template <class T_return, class T_arg>
class base_signal
{
public:
   base_signal() {}
   virtual ~base_signal() {}
   virtual T_return operator() (const T_arg &) = 0;
};
 
template <class T_receiver, class T_return, class T_arg>
class manager_connection;
 
template <class T_return, class T_arg>
class signal
{
public:
   signal() {}
   ~signal() {
       for (_iterator it = _list.begin(); it != _list.end(); it++) {
           delete *it;
       }
       _list.clear();
   }
 
   T_return operator() (const T_arg &arg) {
       for (_iterator it = _list.begin(); it != _list.end(); it++) {
           (*it)->operator()(arg);
       }
   }
 
   template <class T1, class T2, class T3> friend class manager_connection;
 
private:
   std::list<base_signal<T_return, T_arg>* > _list;
   typedef typename std::list<base_signal<T_return, T_arg>* >::iterator _iterator;
 
   void connect(base_signal<T_return, T_arg> *_signal) {
       _list.push_back(_signal);
   }
};
 
template <class T_receiver, class T_return, class T_arg>
class manager_connection : public base_signal<T_return, T_arg>
{
public:
   typedef T_return (T_receiver::*function_type_1)(T_arg);
   typedef T_return (T_receiver::*function_type_2)(const T_arg &);
   typedef T_return (T_receiver::*function_type_3)(T_arg) const;
   typedef T_return (T_receiver::*function_type_4)(const T_arg &) const;
 
   manager_connection(T_receiver *obj, function_type_1 slot, signal<T_return, T_arg> *s) {
       _receiver = obj;
       _slot_1 = slot;
       _index = 1;
       s->connect(this);
   }
 
   manager_connection(T_receiver *obj, function_type_2 slot, signal<T_return, T_arg> *s) {
       _receiver = obj;
       _slot_2 = slot;
       _index = 2;
       s->connect(this);
   }
 
   manager_connection(T_receiver *obj, function_type_3 slot, signal<T_return, T_arg> *s) {
       _receiver = obj;
       _slot_3 = slot;
       _index = 3;
       s->connect(this);
   }
 
   manager_connection(T_receiver *obj, function_type_4 slot, signal<T_return, T_arg> *s) {
       _receiver = obj;
       _slot_4 = slot;
       _index = 4;
       s->connect(this);
   }
 
   ~manager_connection() {
   }
 
   T_return operator()(const T_arg &arg) {
       if (_index == 1)
           return (_receiver->*_slot_1)(arg);
       if (_index == 2)
           return (_receiver->*_slot_2)(arg);
       if (_index == 3)
           return (_receiver->*_slot_3)(arg);
       if (_index == 4)
           return (_receiver->*_slot_4)(arg);
 
       return T_return();
   }
 
private:
   T_receiver *_receiver;
   function_type_1 _slot_1;
   function_type_2 _slot_2;
   function_type_3 _slot_3;
   function_type_4 _slot_4;
   int _index;
   manager_connection() : _receiver(0), _index(0) {}
   manager_connection(const manager_connection &) {}
 
};
 
 
template <class T_receiver, class T_return, class T_arg>
void connect(T_receiver *obj, T_return (T_receiver::*slot)(T_arg), signal<T_return, T_arg> *s) {
   new manager_connection<T_receiver, T_return, T_arg>(obj, slot, s);
}
 
template <class T_receiver, class T_return, class T_arg>
void connect(T_receiver *obj, T_return (T_receiver::*slot)(const T_arg &), signal<T_return, T_arg> *s) {
   new manager_connection<T_receiver, T_return, T_arg>(obj, slot, s);
}
 
template <class T_receiver, class T_return, class T_arg>
void connect(T_receiver *obj, T_return (T_receiver::*slot)(T_arg) const, signal<T_return, T_arg> *s) {
   new manager_connection<T_receiver, T_return, T_arg>(obj, slot, s);
}
 
template <class T_receiver, class T_return, class T_arg>
void connect(T_receiver *obj, T_return (T_receiver::*slot)(const T_arg &) const, signal<T_return, T_arg> *s) {
   new manager_connection<T_receiver, T_return, T_arg>(obj, slot, s);
}
 
#endif // SIGNAL_SLOT_H
 

Архив с проектом прикреплён.
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
brankovic
Гость
« Ответ #8 : Февраль 18, 2011, 19:40 »

Вроде всё работает  Улыбающийся

Теперь всё хорошо и работает так:

Уже симпатично выглядит. А что будет если объект 'b' разрушится (delete например), а потом 'a' испустит сигнал?
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #9 : Февраль 18, 2011, 19:48 »

Вроде всё работает  Улыбающийся

Теперь всё хорошо и работает так:

Уже симпатично выглядит. А что будет если объект 'b' разрушится (delete например), а потом 'a' испустит сигнал?

Известно что: грохинг программы  Смеющийся

Я как раз курю на эту тему)) Если есть предложения буду рад выслушать.
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Февраль 18, 2011, 19:48 »

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

Если возникнет вопрос "пользоваться этим или нет", то мой однозначный ответ "нет", и (возможно) я буду здесь совсем не один. Наверняка Вы сделали все правильно, но каждый template требует как-то "настроить мозги". То есть "врубиться" конечно можно, но это обходится недешево, это надо держать в памяти и не забывать. Когда программист сосредоточен на прикладном аспекте - это особенно болезненно. Нет желанной "простоты использования".

Еще раз - это мое субъективное мнение, не более того  Улыбающийся
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #11 : Февраль 18, 2011, 20:03 »

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

Если возникнет вопрос "пользоваться этим или нет", то мой однозначный ответ "нет", и (возможно) я буду здесь совсем не один. Наверняка Вы сделали все правильно, но каждый template требует как-то "настроить мозги". То есть "врубиться" конечно можно, но это обходится недешево, это надо держать в памяти и не забывать. Когда программист сосредоточен на прикладном аспекте - это особенно болезненно. Нет желанной "простоты использования".

Еще раз - это мое субъективное мнение, не более того  Улыбающийся

Отчасти я Вас понимаю, но с другой стороны не писать же с нуля весь инструментарий? Я например, как и многие другие использую Qt и я совсем не парюсь по поводу сколько там у них template или ещё чего. Я в исходники то их от силы раза 3-4 заглядывал)) Мне остаётся только довериться разработчикам)

Или Вы что-то другое имеете ввиду?
Просто в последним варианте, при использовании этого механизма никаких явных template писать уже не нужно, говорю ж всё максимально приближено к Qt варианту сигнал-слот.
Есть, тут правда ещё недопиленные места, но это детали..
 
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Февраль 18, 2011, 20:20 »

Просто в последним варианте, при использовании этого механизма никаких явных template писать уже не нужно, говорю ж всё максимально приближено к Qt варианту сигнал-слот.
Ну а зачем Вы выбрали тему которая уже известна, более того (в каком-то смысле "авторитетна")? Это, на мой взгляд, неразумно/непрактично. Ну посидеть пару вечерков конечно можно - но на этом дело кончится.

Да и что "такого уж хорошего" в ихней схеме слот/сигнал? Везде есть нормальное "sendEvent" (выполнить немедленно) и "postEvent" (засунуть в очередь). Нормальный программист легко этим пользуется, зачем это накручивать в виде MOC компилятора? В результате мы часто видим "месиво из сигналов" вместо нормального, логичного текста.
Записан
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #13 : Февраль 18, 2011, 20:43 »

Просто в последним варианте, при использовании этого механизма никаких явных template писать уже не нужно, говорю ж всё максимально приближено к Qt варианту сигнал-слот.
Ну а зачем Вы выбрали тему которая уже известна, более того (в каком-то смысле "авторитетна")? Это, на мой взгляд, неразумно/непрактично. Ну посидеть пару вечерков конечно можно - но на этом дело кончится.

Да и что "такого уж хорошего" в ихней схеме слот/сигнал? Везде есть нормальное "sendEvent" (выполнить немедленно) и "postEvent" (засунуть в очередь). Нормальный программист легко этим пользуется, зачем это накручивать в виде MOC компилятора? В результате мы часто видим "месиво из сигналов" вместо нормального, логичного текста.

Ну во-первых лично моё имхо, механизм сигнал-слот - это очень удобная штука. И реализация в Qt это не единственная её реализация. Я не хочу сказать, что в Qt с помощью moc - это круто, напротив, вызов обходится дороже, чем например в схеме сигнал-слот в boost.

Во-вторых я предложил вариант сигнал-слот отличный от реализованного в Qt и отличный от реализации в boost::signal. (как правильно заметил brankovic в boost всё завязано на boost::function)
Это своего рода ещё одна вариация на тему)) И я считаю имеет право быть, хотя бы как объект теоретического исследования)
Короче,
Нет ничего практичнее хорошей теории))
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
m_ax
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2094



Просмотр профиля
« Ответ #14 : Февраль 18, 2011, 22:38 »

Вроде всё работает  Улыбающийся

Теперь всё хорошо и работает так:

Уже симпатично выглядит. А что будет если объект 'b' разрушится (delete например), а потом 'a' испустит сигнал?

В общем самое простое решение я вижу так:
Нужно разорвать соединение, если объект b разрушится.
Это можно сделать, если завернуть объект b в умный указатель, который занулит себя при уничтожении b. А поскольку этот указатель хранится в manager_connection
то перед тем как вызывать operator()() проверять его на валидность.
Как то так.. Сейчас попробую код накатать..   
Записан

Над водой луна двурога. Сяду выпью за Ван Гога. Хорошо, что кот не пьет, Он и так меня поймет..

Arch Linux Plasma 5
Страниц: [1] 2 3 ... 8   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.25 секунд. Запросов: 22.