Re[7]: Указатель на функцию
От: Centaur Россия  
Дата: 22.12.04 18:26
Оценка: 1 (1)
Здравствуйте, DangerDen, Вы писали:

К>>Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!


DD>Можно поподробнее?

DD>Что находится в каждых 32 битах? и как осуществляется вызов по этом 128 битному адресу?

Pointers to member functions are very strange animals, by Raymond Chen, Microsoft, про как это делает Microsoft Visual C++.

И последний комментарий автора к ответу на упражнение:

I am so not going to cover virtual inheritance. That way lies madness.

Re[5]: Указатель на функцию
От: Centaur Россия  
Дата: 22.12.04 18:29
Оценка:
Здравствуйте, Glоbus, Вы писали:

G>Ближе к делу. Если уж ты так реально парился, здается мне что парился ты зря, хотя бы потому, что можно одним движением руки избавиться от вопроса sizeof(void*) < sizeof(void A::*)


G>
G>struct Method {
G> void*    mthdOwner;
G> void (A::*mthdFunction)();
G> Method(void* Owner,...)
G> {
G>  va_list mthd;
G>  va_start(mthd,Owner);
G>  mthdOwner    = Owner;
G>  mthdFunction    = va_arg(mthd,void A::*);
G>  va_end(mthd);
G> }
G>};

G>


Рискну предлжить тогда избавиться от ...:

struct Method {
  void*    mthdOwner;
  void (A::*mthdFunction)();
  Method(void* Owner, void (A::*method)())
  {
    mthdOwner = Owner;
    mthdFunction = method;
  }
};

Так к чему мы это всё?
Re[5]: Указатель на функцию
От: Андрей Тарасевич Беларусь  
Дата: 22.12.04 19:10
Оценка: 1 (1)
Здравствуйте, eao197, Вы писали:


E>>>Результат работы под VC++ 7.1:

E>>>sizeof(vp): 4, sizeof(mp): 4

АТ>>Такой результат ты получишь только при "агрессивных" установках компиляции, при которых указатели на методы классов в MSVC++ в общем случае работают некорректно. При включении "нормальных" установок компиляции размер указателя на метод класса становится равным 16. Есть "промежуточная" установка, при котором размер будет равен 8.


E>Запускал компиляцию так:

E>cl -GX meth_ptr_size.cpp

E>Потом просто запустил meth_ptr_size.exe


Попробуй теперь добавить '/vmg /vmm' или '/vmg /vmv'.
Best regards,
Андрей Тарасевич
Re[6]: Указатель на функцию
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 23.12.04 08:27
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Здравствуйте, eao197, Вы писали:


АТ>Попробуй теперь добавить '/vmg /vmm' или '/vmg /vmv'.


Попробовал. /vmm: 8 байт, /vmv: 16 байт.

Однако, фокус-то здесь не только в /vmm (/vmv), а еще и в /vmg, которая должна использоваться, если указатель на метод определяется еще до того, как будет определен сам класс. В моем примере класс определен, поэтому компилятор смог сам определить оптимальный размер указателя на метод (4 байта).

Если же переписать мой пример так:

#include <iostream>

class    A;

typedef void (A::*meth_ptr_t)();

int
main()
  {
    void * vp = 0;
    meth_ptr_t mp = 0;

    std::cout << "sizeof(vp): " << sizeof(vp) << ", "
      << "sizeof(mp): " << sizeof(mp) << std::endl;

    return 0;
  }


И скомпилировать с найтройками по-умолчанию:
cl -GX meth_ptr_size-2.cpp

То получается:
sizeof(vp): 4, sizeof(mp): 16

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

Сам я про эту фишку VC++ не знал, спасибо.
Компиляторы GNU C++ 3.3.3, Borland C++ 5.6, Digital Mars C++ v.8.35 на это не заморачиваются, у них всегда указатель на метод одного и того же размера (8 у GNU C++, 12 у BC++, 4 у DMC).

Для себя сделал вывод, что с компилятором VC++ можно поиметь приключения вот в таком случае.

Файл a_forward.hpp
class    A;
typedef void (A::*method_of_A)();

void
do_something( A * a, method_of_A m );


Файл do_something.cpp
#include "a_forward.hpp"

void
do_something( A * a, method_of_A m )
  {
    (a->*m)();
  }


Файл a_implementation.hpp
class    A
  {
  public :
    void f();
  };


Файл main.cpp
#include "a_implementation.hpp"
#include "a_forward.hpp"

void
A::f() {}

int
main()
  {
    A a;
    do_something( &a, &A::f );

    return 0;
  }


Компилируем: cl -o test.exe main.cpp do_something.cpp
Запускаем и получаем ошибку.

Ни с GNU C++, ни с Borland C++, ни с Digital Mars C++, при использовании настроек по-умолчанию проблем нет.

IMHO в Microsoft перемудрили.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[5]: Указатель на функцию
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 23.12.04 08:41
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:


АТ>Указатели на методы классов в С++ обладают рядом уникальных свойств. На них стандартом языка накладываются определенные требования, удовлетворить которым можно только путем помещения в указатель дополнительной информации, наряду с банальным адресом точки входа в метод. По этой причине, если на некоторой платформа размер адреса равен 32-м битам, то размер корректно реализованного указателя на метод класса всегда заведомо больше 32-х бит (64 бита, например)....


Я не специалист по внутреннему устройству компиляторов, но это утверждение представляется мне излишне категоричным. Теоритически, вполне можно представить себе ситуацию, когда указатель на метод является всего лишь "дескриптором", указателем на структуру, в которой и находятся все необходимые для вызова значения. Тогда указатель на метод вполне может оказаться 32-х битовым, а вот размер вспомогательной структуры -- это уже другой вопрос. Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: Указатель на функцию
От: korzhik Россия  
Дата: 23.12.04 08:45
Оценка: 4 (2)
Здравствуйте, eao197, Вы писали:

E>Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.


Digital Mars C++, uses a different optimization. For single-inheritance classes, a member function pointer is just the address of the function. When more complex inheritance is involved, the member function pointer points to a 'thunk' function, which performs the necessary adjustments to the this pointer, and then calls the real member function. One of these little thunk functions is created for every member function that's involved in multiple inheritance. This is the most efficient implementation.

Re[7]: Указатель на функцию
От: Андрей Тарасевич Беларусь  
Дата: 23.12.04 09:09
Оценка:
Здравствуйте, eao197, Вы писали:

E>Однако, фокус-то здесь не только в /vmm (/vmv), а еще и в /vmg, которая должна использоваться, если указатель на метод определяется еще до того, как будет определен сам класс. В моем примере класс определен, поэтому компилятор смог сам определить оптимальный размер указателя на метод (4 байта).


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

Вот простенький пример

struct A {
  int i;
  A() : i(1) {}
};

struct B {
  int i;
  B() : i(2) {}
};

struct C : A, B {
  int i;
  C() : i(3) {}
  void print() { std::cout << i << std::endl; }
};

void call(B* pb, void (B::*pm)()) {
  (pb->*pm)();
}

int main() {
  C c;
  call(&c, static_cast<void (B::*)()>(&C::print));
}


Это — легальный С++ код. И при выполнении из функции 'call' должен вызваться метод 'C::print' с правильно сформированным указателем 'this' класса 'C'. Т.е. в результате должно напечататься '3'. Однако, если попробовать скомпилировать этот пример в режиме автоматического определения размера указателя, то печатать он будет всякую белиберду. А вот при компиляции с /vmm или /vmv все будет работать корректно. Т.е. надо иметь в виду, что MSVC++-шный режим автоматического определения размера указателя в общем случае приводит к некорректному коду. А именно, проблемы могут возникнуть при приведении указателя на метод вверх по иерархии (как в моем примере).

E>Сам я про эту фишку VC++ не знал, спасибо.

E>Компиляторы GNU C++ 3.3.3, Borland C++ 5.6, Digital Mars C++ v.8.35 на это не заморачиваются, у них всегда указатель на метод одного и того же размера (8 у GNU C++, 12 у BC++, 4 у DMC).

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

E>Для себя сделал вывод, что с компилятором VC++ можно поиметь приключения вот в таком случае.


Вот вот, еще один пример.

E>IMHO в Microsoft перемудрили.


В MS сделали фичу, которой надо пользоваться с определенной долей осторожности. В принципе, полезность в этой фиче имеется, ибо в ситуациях с одиночным (и только одиночным) обычным наследованием действительно можно обойтись "маленькими" указателями на методы. В многих программах только такое наследование и используется, что дает возможность сэкономить память и повысить производительность вызова. Вот чего не стоило бы делать MS, так это придавать этой фиче эдакий дух "универсальной автоматичности". Это не должно делаться автоматически, это должен быть сознательный выбор программиста, имеющего представление о потенциальных граблях такого выбора.
Best regards,
Андрей Тарасевич
Re[7]: Указатель на функцию
От: Андрей Тарасевич Беларусь  
Дата: 23.12.04 09:16
Оценка:
Здравствуйте, korzhik, Вы писали:

E>>Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.


K>

K>Digital Mars C++, uses a different optimization. For single-inheritance classes, a member function pointer is just the address of the function. When more complex inheritance is involved, the member function pointer points to a 'thunk' function, which performs the necessary adjustments to the this pointer, and then calls the real member function. One of these little thunk functions is created for every member function that's involved in multiple inheritance. This is the most efficient implementation.


Это описание наводит на мысль что Digital Mars C++ реализован в соответствии со старым черновым вариантом спецификации языка, в котором еще не было разрешения приводить указатели на методы классов вверх по иерархии (при помощи 'static_cast'). У кого-нибудь есть возможность попробовать скомпилировать и выполнить код вот из этого
Автор: Андрей Тарасевич
Дата: 23.12.04
моего сообщения при помощи этого компилятора и сообщить, что получается в результате.
Best regards,
Андрей Тарасевич
Re[8]: Указатель на функцию
От: korzhik Россия  
Дата: 23.12.04 09:39
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Это описание наводит на мысль что Digital Mars C++ реализован в соответствии со старым черновым вариантом спецификации языка, в котором еще не было разрешения приводить указатели на методы классов вверх по иерархии (при помощи 'static_cast'). У кого-нибудь есть возможность попробовать скомпилировать и выполнить код вот из этого
Автор: Андрей Тарасевич
Дата: 23.12.04
моего сообщения при помощи этого компилятора и сообщить, что получается в результате.


Digital Mars C/C++ Compiler Version 8.41n печатает
3

все указатели на функции-члены равны 4. (я проверил)
Re[8]: Указатель на функцию
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 23.12.04 09:58
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:


АТ>Дело в том (это тут уже обсуждалось) что в общем случае определить "оптимальный" размер указателя автоматически невозможно. В С++ разрешается приведение указателей на члены классов как вверх, так и вниз по иерархии объектов (в соответсвии с правилами контравариантности).


IMHO приведение вверх по иерархии недопустимо в общем случае. См. ниже. Хотя я могу ошибаться, т.к. не являюсь знатоком стандарта C++ и не знаю правила контравариантности. Но может быть вы и правы.

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


АТ>Вот простенький пример


АТ>
АТ>struct A {
АТ>  int i;
АТ>  A() : i(1) {}
АТ>};

АТ>struct B {
АТ>  int i;
АТ>  B() : i(2) {}
АТ>};

АТ>struct C : A, B {
АТ>  int i;
АТ>  C() : i(3) {}
АТ>  void print() { std::cout << i << std::endl; }
АТ>};

АТ>void call(B* pb, void (B::*pm)()) {
АТ>  (pb->*pm)();
АТ>}

АТ>int main() {
АТ>  C c;
АТ>  call(&c, static_cast<void (B::*)()>(&C::print));
АТ>}
АТ>


АТ>Это — легальный С++ код. И при выполнении из функции 'call' должен вызваться метод 'C::print' с правильно сформированным указателем 'this' класса 'C'. Т.е. в результате должно напечататься '3'. Однако, если попробовать скомпилировать этот пример в режиме автоматического определения размера указателя, то печатать он будет всякую белиберду. А вот при компиляции с /vmm или /vmv все будет работать корректно. Т.е. надо иметь в виду, что MSVC++-шный режим автоматического определения размера указателя в общем случае приводит к некорректному коду. А именно, проблемы могут возникнуть при приведении указателя на метод вверх по иерархии (как в моем примере).


Позволю себе не согласиться с легальностью этого кода. При компиляции VC++ с настройками по умолчанию компилятор выдает предупреждение (на строку call(&c...)):

tarasevich.cpp(25) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code


Если же static_cast убрать, то скомпилировать ваш пример вообще не получиться:

tarasevich.cpp(25) : error C2664: 'call' : cannot convert parameter 2 from 'void (__thiscall C::* )(void)' to 'void (__thiscall B::* )(void)'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast


Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).

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

Когда VC++ знает описание класса и видит определение типа указателя на метод класса он может сделать разумные предположения о необходимом размере указателя на методы именно этого класса (включая и унаследованные методы). Если у нас есть класс A, на метод которого мы хотим определить указатель, и VC++ видит описание этого класса (значит и описания всех его родительских классов), то VC++ точно знает, какой размер указателя ему для этого нужен. Если же описания класса A в точке определения указателя на метод A еще нет (пример описан в MSDN в документации по /vmb и /vmg), то VC++ не знает про наследование A. Поэтому VC++ не знает, будет ли указатель указывать на метод самого A, будет ли указывать на метод его базового класса, будет ли этот базовый класс виртуальным базовым классом или одним из базовых при множественном наследовании, и т.д. и т.п. Поэтому VC++ выбирает для неизвестных классов максимальный размер указателя.

Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.

E>>IMHO в Microsoft перемудрили.


АТ>В MS сделали фичу, которой надо пользоваться с определенной долей осторожности....


Ну да, всего лишь еще одна фича. Про которую нужно не только знать, но и помнить. До сегодняшнего дня я про нее вообще не знал.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[6]: Указатель на функцию
От: Кодёнок  
Дата: 23.12.04 10:16
Оценка: 1 (1)
E>Я не специалист по внутреннему устройству компиляторов, но это утверждение представляется мне излишне категоричным. Теоритически, вполне можно представить себе ситуацию, когда указатель на метод является всего лишь "дескриптором", указателем на структуру, в которой и находятся все необходимые для вызова значения. Тогда указатель на метод вполне может оказаться 32-х битовым, а вот размер вспомогательной структуры -- это уже другой вопрос. Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.

Кстати, да Когда мне ооочень нужно было иметь указатель размером void* на метод, я создал статические переменные, хранящие указатели на все методы, адрес которых я беру, и брал указатель на эту статическую переменную. Этот метод кстати, отлично компилируется и работает с любыми настройками VC++, если опять же не кастовать вверх по иерархии.

struct TClass : A, B, C, virtual D, virtual E
{
};

static void (TClass::*)() _ptm_TClass_Run = &TClass::Run;

void *p = &_ptm_TClass_Run;


Для автоматизации кастинга к void* и обратно и вызова можно написать набор шаблонных классов и функций, которые будут где-нибудь в статическом массиве будут хранить соответствия между void*-ом и указателем на члены, добавляя в рантайме в момент приведения типа.
Re[9]: Указатель на функцию
От: Андрей Тарасевич Беларусь  
Дата: 23.12.04 18:36
Оценка:
Здравствуйте, eao197, Вы писали:

АТ>>Дело в том (это тут уже обсуждалось) что в общем случае определить "оптимальный" размер указателя автоматически невозможно. В С++ разрешается приведение указателей на члены классов как вверх, так и вниз по иерархии объектов (в соответсвии с правилами контравариантности).


E>IMHO приведение вверх по иерархии недопустимо в общем случае. См. ниже. Хотя я могу ошибаться, т.к. не являюсь знатоком стандарта C++ и не знаю правила контравариантности. Но может быть вы и правы.


Согласно стандарту языка, приведение такого указателя вверх по иерархии разрешается всегда, когда разрешается приведение вниз. Этого, в принципе, достаточно для того, чтобы сказать, что такое приведение допустимо в общем случае.

АТ>>...

АТ>>Это — легальный С++ код. И при выполнении из функции 'call' должен вызваться метод 'C::print' с правильно сформированным указателем 'this' класса 'C'. Т.е. в результате должно напечататься '3'. Однако, если попробовать скомпилировать этот пример в режиме автоматического определения размера указателя, то печатать он будет всякую белиберду. А вот при компиляции с /vmm или /vmv все будет работать корректно. Т.е. надо иметь в виду, что MSVC++-шный режим автоматического определения размера указателя в общем случае приводит к некорректному коду. А именно, проблемы могут возникнуть при приведении указателя на метод вверх по иерархии (как в моем примере).

E>Позволю себе не согласиться с легальностью этого кода. При компиляции VC++ с настройками по умолчанию компилятор выдает предупреждение (на строку call(&c...)):


E>

E>tarasevich.cpp(25) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code


Этот код соверешено легален. А предупреждение — это ни что иное, как недостоток выбранного режима компиляции. Версия MSVC++ 6 проглатывает этот код молча, но программа потом работает неправильно (при умолчательных установках компиляции). Ты, по-видимому, используешь MSVC++ 7, в котором теперь сделали предупреждение. Я бы на месте MS вообще отказался бы компилировать код и предложил бы пользоваетелю сменить модель указателя.

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

E>Если же static_cast убрать, то скомпилировать ваш пример вообще не получиться:


E>

E>tarasevich.cpp(25) : error C2664: 'call' : cannot convert parameter 2 from 'void (__thiscall C::* )(void)' to 'void (__thiscall B::* )(void)'
E> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast


'static_cast' убирать, разумеется, не надо. Правила контравариантности разрешают неявное приведение вниз по иерархии и требуют 'static_cast' для приведения вверх по иерерхии. Симметричным образом, например, правила ковариантности (управляющие приведением обычных указателей) наоборот разрешают неявное приведение вверх, но требуют явного каста для приведения вниз.

E>Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).


Да, но язык С++ и не требует этого при выполнении приведения. Язык С++ требует наличия члена в классе только в момент обращения к этому члену через указатель, но не в моемент преобразования типов.

E>Года 4 назад мне потребовалось плотно поиспользовать указатели на методы класса и я с удивлением обнаружил, что компилятор не позволяет просто так приводить указатель на метод производного класса к методу базового класса. Стал разбираться почему и вычитал, сейчас не помню у кого, что это делать нельзя. С тех пор не делаю и проблем не имею.


Это уже зависит от того, что ты имеешь в виду под "нельзя".

"Нельзя = не рекомендуется, т.к. опасно"? Это действительно так, но это лишь означает, что использовать данную возможноть надо только в том случае, если это действительно необходимо. Точно так же и обычные указатели на объекты приводит вверх по иерархии "нельзя" (по аналогичной причине), тем не менее такая возможность в языке есть. Кроме того, подобное "нельзя" в этой дискуссии, вследствие ее специфики, силы не имеет.

"Нельзя = запрещено спецификацией языка"? Это соврешенно не верно. Спецификация языка явно разрешает такое приведение. Когда-то давно в самой первой версии черновика стандарта языка это было запрещено. Так что если ты читал что-то старинное, то ты действительно можешь наткнутся на такое "нельзя". Но уже во втором черновике (не говоря уже о финальном стандарте) такое приведение было разрешено.

E>Когда VC++ знает описание класса и видит определение типа указателя на метод класса он может сделать разумные предположения о необходимом размере указателя на методы именно этого класса (включая и унаследованные методы). Если у нас есть класс A, на метод которого мы хотим определить указатель, и VC++ видит описание этого класса (значит и описания всех его родительских классов), то VC++ точно знает, какой размер указателя ему для этого нужен. Если же описания класса A в точке определения указателя на метод A еще нет (пример описан в MSDN в документации по /vmb и /vmg), то VC++ не знает про наследование A. Поэтому VC++ не знает, будет ли указатель указывать на метод самого A, будет ли указывать на метод его базового класса, будет ли этот базовый класс виртуальным базовым классом или одним из базовых при множественном наследовании, и т.д. и т.п. Поэтому VC++ выбирает для неизвестных классов максимальный размер указателя.

E>Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.

Последнее соврешенно не верно. Никакая "ошибка программиста" через 'static_cast' не подавляется. 'static_cast', по своей идее, не может подавлять никаких "ошибок программиста" (для этого предназначен 'reinterpret_cast' . Использованная мною конверсия является соврешенно легальной функциональностью 'static_cast', четко описанной в отдельном разделе стандарта 5.2.9/9. Меня всегда удивляло, почему программисты с достаточной степенью готовности вопринимают идею ковариантности для обычных указателей, а вот в существовоание контравариантности дял указателей на члены не верят и считают, что без хака тут не обойтись.
Best regards,
Андрей Тарасевич
Re[6]: Указатель на функцию
От: Андрей Тарасевич Беларусь  
Дата: 23.12.04 18:42
Оценка:
Здравствуйте, eao197, Вы писали:

АТ>>Указатели на методы классов в С++ обладают рядом уникальных свойств. На них стандартом языка накладываются определенные требования, удовлетворить которым можно только путем помещения в указатель дополнительной информации, наряду с банальным адресом точки входа в метод. По этой причине, если на некоторой платформа размер адреса равен 32-м битам, то размер корректно реализованного указателя на метод класса всегда заведомо больше 32-х бит (64 бита, например)....


E>Я не специалист по внутреннему устройству компиляторов, но это утверждение представляется мне излишне категоричным. Теоритически, вполне можно представить себе ситуацию, когда указатель на метод является всего лишь "дескриптором", указателем на структуру, в которой и находятся все необходимые для вызова значения. Тогда указатель на метод вполне может оказаться 32-х битовым, а вот размер вспомогательной структуры -- это уже другой вопрос. Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.


Я с этим согласен. Я только замечу, что в моем вышепроцитированном сообщении речь шла, скорее, о количестве информации, которая необходима для правильного вызова метода класса через указатель, т.е. о некоем "совокупном" размере указателя, включающем в себя все его "кусочки", где бы они ни были разбросаны.

На такой платформе, наверное, можно будет более-менее безопасно приводить указатели на методы класстов к типу 'void*', хоть это и будет по-прежнему будет хаком.
Best regards,
Андрей Тарасевич
Re[10]: Указатель на функцию
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 24.12.04 09:44
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:


АТ>Этот код соверешено легален. А предупреждение — это ни что иное, как недостоток выбранного режима компиляции. Версия MSVC++ 6 проглатывает этот код молча, но программа потом работает неправильно (при умолчательных установках компиляции). Ты, по-видимому, используешь MSVC++ 7, в котором теперь сделали предупреждение. Я бы на месте MS вообще отказался бы компилировать код и предложил бы пользоваетелю сменить модель указателя.


АТ>Это хорошо, что компилятор сообщает о проблеме, но к легальности этого кода это никакого отношения не имеет. Код легален, а пердупреждение говорит только о том, что в данном режиме компиляции данный компилятор не в состоянии его правильно скомпилировать.


Вот, что я компилировал:

#include <iostream>

struct A {
  int i;
  A() : i(1) {}
};

struct B {
  int i;
  B() : i(2) {}
};

struct C : A, B {
  int i;
  C() : i(3) {}
  void print() { std::cout << i << std::endl; }
};

void call(B* pb, void (B::*pm)()) {
  (pb->*pm)();
}

int main() {
  C c;
  call(&c, &C::print );

  return 0;
}


Результат компиляции VC++ 7.1 я уже приводил.
Результат компиляции Borland C++:

Borland C++ 5.6 for Win32 Copyright (c) 1993, 2002 Borland
tarasevich.cpp:
Error E2034 tarasevich.cpp 25: Cannot convert 'void (C::*)()' to 'void (B::*)()' in function main()
Error E2342 tarasevich.cpp 25: Type mismatch in parameter 'pm' (wanted 'void (B::*)()', got 'void (C::*)()') in function main()
*** 2 errors in Compile ***

Результат компиляции GNU C++ 3.3.3 (cygwin):

tarasevich.cpp: In function `int main()':
tarasevich.cpp:25: error: cannot convert `void (C::*)()' to `void (B::*)()' for
argument `2' to `void call(B*, void (B::*)())'

Результат компиляции Comeau C++ 4.3.3:

Thank you for testing your code with Comeau C/C++!
Tell others about http://www.comeaucomputing.com/tryitout !

Your Comeau C/C++ test results are as follows:

Comeau C/C++ 4.3.3 (Aug 6 2003 15:13:37) for ONLINE_EVALUATION_BETA1
Copyright 1988-2003 Comeau Computing. All rights reserved.
MODE:strict errors C++

"ComeauTest.c", line 25: error: argument of type "void (C::*)()" is incompatible
with parameter of type "void (B::*)()"
call(&c, &C::print );
^

1 error detected in the compilation of "ComeauTest.c".
In strict mode, with -tused, Compile failed


Поэтому я думаю, что проблема не в особенностях конкретного компилятора, а в корректности кода.

АТ>'static_cast' убирать, разумеется, не надо. Правила контравариантности разрешают неявное приведение вниз по иерархии и требуют 'static_cast' для приведения вверх по иерерхии. Симметричным образом, например, правила ковариантности (управляющие приведением обычных указателей) наоборот разрешают неявное приведение вверх, но требуют явного каста для приведения вниз.


E>>Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).


АТ>Да, но язык С++ и не требует этого при выполнении приведения. Язык С++ требует наличия члена в классе только в момент обращения к этому члену через указатель, но не в моемент преобразования типов.


Вот следствие из этого замечания:

#include <stdio.h>

struct A {
  int i;
  A() : i(1) {}
};

struct B {
  int i;
  B() : i(2) {}
};

typedef void (B::*meth_t)();

struct C : A, B {
  int i;
  C() : i(3) {}
  void print() { printf( "C::print: %d\n", i ); }
};

struct D : B {
  int i;
  D() : i(4) {}
  void print() { printf( "D::print: %d\nFormat C: complete!\n", i ); }
};

void call(B* pb, meth_t pm ) {
  (pb->*pm)();
}

int main() {
  meth_t pm = static_cast< meth_t >( &C::print );

  C c;
  call( &c, pm );

  D d;
  call( &d, pm );

  return 0;
}


Результатом работы является:

C::print: 3
C::print: 4


Т.е. для объекта типа D вызывается метод из типа C! Причем типы D и C между собой не связаны отношением наследования.

Понятно, что в этом примере я передергиваю: нельзя применять результат static_cast< meth_t >( &C::print ) к объектам, отличным от C. Но ведь согласно синтаксису языка здесь все нормально. Т.е. это вполне легальный код, с нелегальными результатами, однако.
Но проблем бы не было, если бы я не использовал static_cast. А со static_cast я силовым методом принуждаю C++ компилятор считать этот код легальным.

E>>Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.


АТ>Последнее соврешенно не верно. Никакая "ошибка программиста" через 'static_cast' не подавляется. 'static_cast', по своей идее, не может подавлять никаких "ошибок программиста" (для этого предназначен 'reinterpret_cast' . Использованная мною конверсия является соврешенно легальной функциональностью 'static_cast', четко описанной в отдельном разделе стандарта 5.2.9/9. Меня всегда удивляло, почему программисты с достаточной степенью готовности вопринимают идею ковариантности для обычных указателей, а вот в существовоание контравариантности дял указателей на члены не верят и считают, что без хака тут не обойтись.


Посмотрел пункт 5.2.9/9 стандарта, а заодно и ссылку оттуда на пункт 4.11. Казуистика еще та, сразу вспомнилась русская пословица про "закон, что дышло..." + еще мои (не)знания английского языка К сожалению, стандарт C++ далек от совершенства, допускает различные варианты интерпритации (что видно на особенностях разных компиляторов).

Но для себя я сделал такой вывод: static_cast для указателей на члены класса можно применять, только если есть разрешенное обычное преобразование указателей на члены класса. А разрешенность преобразований указателей на члены классов описывается в 4.11. И для меня оттуда следует, что например указатель на член класса B можно привести к указателю на член класса D, если класс B является базовым для D. Что понятно, т.к. все атрибуты/методы из B находятся в D. Следовательно, обратное преобразование возможно, но необходимо, чтобы преобразуемый указатель указывал на атрибут/метод из B, а не из D. Исходя из этого два следующих примера будут вполне корректны:

#include <iostream>

struct A {
  int i;
  A() : i(1) {}
};

struct B {
  int i;
  B() : i(2) {}

  void print() { std::cout << i << std::endl; }
};

struct C : A, B {
  int i;
  C() : i(3) {}
};

void call(B* pb, void (B::*pm)()) {
  (pb->*pm)();
}

int main() {
  C c;
  call(&c, &C::print );

  return 0;
}


Что понятно, т.к. C::print — это синоним для B::print.

А вот здесь static_cast действительно необходим:

#include <stdio.h>

struct A {
  int i;
  A() : i(1) {}
};

struct B {
  int i;
  B() : i(2) {}

  virtual void print() { printf( "B::print: %d\n", i ); }
};

struct C : A, B {
  int i;
  C() : i(3) {}

  virtual void print() { printf( "C::print: %d\n", i ); }
};

void call(B* pb, void (B::*pm)()) {
  (pb->*pm)();
}

int main() {
  C c;
  call(&c, static_cast< void (B::*)() >( &C::print ) );

  return 0;
}


И использование static_cast здесь вполне легально, т.к. метод print есть в классе B, базовом для C.

Ну и в догонку пример того, как static_cast (будучи, в определенной степени синонимом преобразования типов в стиле C) позволяет легко прострелить себе ногу еще в одном случае:

char c = ...;
unsigned int u = static_cast< unsigned int >( c );


В этом легальном C++ коде мы получим проблемы, если тип char будет знаковым, а значение c — отрицательным. И это как раз тот случай, когда "Нельзя = не рекомендуется, т.к. опасно" лично для меня более важно, чем "Нельзя = запрещено спецификацией языка". Но это уже совсем другая история.

Ну а чтобы закрыть исходную тему, поднятую г.Якубовским, цитата из стандарта C++ (пункт 4.11/2, сноска 52 на странице 61):

... In particular, a pointer to member cannot be converted to a void*.



SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[11]: Указатель на функцию
От: Андрей Тарасевич Беларусь  
Дата: 24.12.04 10:17
Оценка:
Здравствуйте, eao197, Вы писали:

АТ>>Этот код соверешено легален. А предупреждение — это ни что иное, как недостоток выбранного режима компиляции. Версия MSVC++ 6 проглатывает этот код молча, но программа потом работает неправильно (при умолчательных установках компиляции). Ты, по-видимому, используешь MSVC++ 7, в котором теперь сделали предупреждение. Я бы на месте MS вообще отказался бы компилировать код и предложил бы пользоваетелю сменить модель указателя.


АТ>>Это хорошо, что компилятор сообщает о проблеме, но к легальности этого кода это никакого отношения не имеет. Код легален, а пердупреждение говорит только о том, что в данном режиме компиляции данный компилятор не в состоянии его правильно скомпилировать.


E>Вот, что я компилировал:

E>...
E>Результат компиляции VC++ 7.1 я уже приводил.
E>Результат компиляции Borland C++:
E>...
E>Поэтому я думаю, что проблема не в особенностях конкретного компилятора, а в корректности кода.

Не понимаю. Я же говорю о моем коде в его исходной форме. Ты же компилировал свою модифицированную версию кода, из которой ты зачем-то убрал 'static_cast'. Без 'static_cast' код действительно некорректен, компилироваться не должен и не будет, о чем я уже говорил в моем предыдущем сообщении. Что именно ты хочешь сказать, приводя здесь результаты компиляции модифицированного кода?

АТ>>'static_cast' убирать, разумеется, не надо. Правила контравариантности разрешают неявное приведение вниз по иерархии и требуют 'static_cast' для приведения вверх по иерерхии. Симметричным образом, например, правила ковариантности (управляющие приведением обычных указателей) наоборот разрешают неявное приведение вверх, но требуют явного каста для приведения вниз.


E>>>Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).


АТ>>Да, но язык С++ и не требует этого при выполнении приведения. Язык С++ требует наличия члена в классе только в момент обращения к этому члену через указатель, но не в моемент преобразования типов.


E>Вот следствие из этого замечания:


E>
E>#include <stdio.h>

E>struct A {
E>  int i;
E>  A() : i(1) {}
E>};

E>struct B {
E>  int i;
E>  B() : i(2) {}
E>};

E>typedef void (B::*meth_t)();

E>struct C : A, B {
E>  int i;
E>  C() : i(3) {}
E>  void print() { printf( "C::print: %d\n", i ); }
E>};

E>struct D : B {
E>  int i;
E>  D() : i(4) {}
E>  void print() { printf( "D::print: %d\nFormat C: complete!\n", i ); }
E>};

E>void call(B* pb, meth_t pm ) {
E>  (pb->*pm)();
E>}

E>int main() {
E>  meth_t pm = static_cast< meth_t >( &C::print );

E>  C c;
E>  call( &c, pm );

E>  D d;
E>  call( &d, pm );

E>  return 0;
E>}
E>


E>Результатом работы является:

E>

E>C::print: 3
E>C::print: 4


E>Т.е. для объекта типа D вызывается метод из типа C! Причем типы D и C между собой не связаны отношением наследования.


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

E>Понятно, что в этом примере я передергиваю: нельзя применять результат static_cast< meth_t >( &C::print ) к объектам, отличным от C.


Совершенно верно.

E>Но ведь согласно синтаксису языка здесь все нормально.


Согласно синтаксису — да, все нормально. Но какое это имеет значение?

E>Т.е. это вполне легальный код, с нелегальными результатами, однако.


Это так называемый well-formed код. А вот легальным я бы его называть не стал. Хотя это уже зависит от твоей трактовки слова "легальный".

E>Но проблем бы не было, если бы я не использовал static_cast. А со static_cast я силовым методом принуждаю C++ компилятор считать этот код легальным.


Прекрасно. Но это не аргумент. В С++ имеется бесчисленное множество мест, где синтаксически правильный код является ошибочным. И где полезные в одних контекстах свойства могут приводить к "сломанному" коду в других. Таков С++.

E>>>Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.


АТ>>Последнее соврешенно не верно. Никакая "ошибка программиста" через 'static_cast' не подавляется. 'static_cast', по своей идее, не может подавлять никаких "ошибок программиста" (для этого предназначен 'reinterpret_cast' . Использованная мною конверсия является соврешенно легальной функциональностью 'static_cast', четко описанной в отдельном разделе стандарта 5.2.9/9. Меня всегда удивляло, почему программисты с достаточной степенью готовности вопринимают идею ковариантности для обычных указателей, а вот в существовоание контравариантности дял указателей на члены не верят и считают, что без хака тут не обойтись.


E>Посмотрел пункт 5.2.9/9 стандарта, а заодно и ссылку оттуда на пункт 4.11. Казуистика еще та, сразу вспомнилась русская пословица про "закон, что дышло..." + еще мои (не)знания английского языка К сожалению, стандарт C++ далек от совершенства, допускает различные варианты интерпритации (что видно на особенностях разных компиляторов).


E>Но для себя я сделал такой вывод: static_cast для указателей на члены класса можно применять, только если есть разрешенное обычное преобразование указателей на члены класса. А разрешенность преобразований указателей на члены классов описывается в 4.11. И для меня оттуда следует, что например указатель на член класса B можно привести к указателю на член класса D, если класс B является базовым для D. Что понятно, т.к. все атрибуты/методы из B находятся в D. Следовательно, обратное преобразование возможно, но необходимо, чтобы преобразуемый указатель указывал на атрибут/метод из B, а не из D.


Нет, вот этого последноего требования в стандарте нет. Стандарт разрешает 'static_cast' преобразование любых указателей типа 'pointer to member of D' к соотвествующему типу 'pointer to member of B', если обратное преобразование удовлетворяет требованиям стандартного преобразования, описанного в 4.11. Никаких ограничений на то, куда именно указывает исходный указатель в момент преобразования не накладывается.

Дополнительные ограничения появляются только в 5.5/4, где сказано, что динамический тип объекта, стоящего слева от операторов '.*' или '->*' должен содержать указуемый член, иначе — поведение не определено. Т.е. ограничения на то, куда именно указывает указатель, вступают в силу только в момент разадресации этого указателя, но не раньше. Это именно то требование, которое ты нарушил в своем примере выше.

E>Ну и в догонку пример того, как static_cast (будучи, в определенной степени синонимом преобразования типов в стиле C) позволяет легко прострелить себе ногу еще в одном случае:


E>
E>char c = ...;
E>unsigned int u = static_cast< unsigned int >( c );
E>


E>В этом легальном C++ коде мы получим проблемы, если тип char будет знаковым, а значение c — отрицательным. И это как раз тот случай, когда "Нельзя = не рекомендуется, т.к. опасно" лично для меня более важно, чем "Нельзя = запрещено спецификацией языка". Но это уже совсем другая история.


Не совсем понимаю, о каких "проблемах" идет речь. Если значение 'c' отрицательно, то сработает арифметика по модулю, который подчиняются беззнаковые типы, и переменная 'u' проинициализируется значенем 'UINT_MAX + c + 1'. 'static_cast' тут никаким боком совершенно ни при чем. Приведенный тобой код строго эквивалентен простому

unsigned int u = c;


т.е. никакого отношения к 'static_cast' происходящее не имеет вообще. Подводным камнем в этом примере является только зависящая от реализации знаковость или беззнаковость типа 'char'. При чем здесь 'static_cast'?
Best regards,
Андрей Тарасевич
Re[12]: Указатель на функцию
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 24.12.04 11:43
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Не понимаю. Я же говорю о моем коде в его исходной форме. Ты же компилировал свою модифицированную версию кода, из которой ты зачем-то убрал 'static_cast'. Без 'static_cast' код действительно некорректен, компилироваться не должен и не будет, о чем я уже говорил в моем предыдущем сообщении. Что именно ты хочешь сказать, приводя здесь результаты компиляции модифицированного кода?


Очевидно, в какой-то момент я не понял, почему VC++ 6.0 проглатывает какой-то код, а VC++ 7.1 на нем выдает ошибку. Т.к. со static_cast все компиляторы компилируют твой исходный код без ошибок, то я подумал, что речь должна идти о варианте без static_cast. И ошибся.

Приводя результаты компиляции я хотел показать, что без static_cast все из доступных мне компиляторов выдали ошибку.

АТ>Прекрасно. Но это не аргумент. В С++ имеется бесчисленное множество мест, где синтаксически правильный код является ошибочным. И где полезные в одних контекстах свойства могут приводить к "сломанному" коду в других. Таков С++.


АТ>Нет, вот этого последноего требования в стандарте нет. Стандарт разрешает 'static_cast' преобразование любых указателей типа 'pointer to member of D' к соотвествующему типу 'pointer to member of B', если обратное преобразование удовлетворяет требованиям стандартного преобразования, описанного в 4.11. Никаких ограничений на то, куда именно указывает исходный указатель в момент преобразования не накладывается.


АТ>Дополнительные ограничения появляются только в 5.5/4, где сказано, что динамический тип объекта, стоящего слева от операторов '.*' или '->*' должен содержать указуемый член, иначе — поведение не определено. Т.е. ограничения на то, куда именно указывает указатель, вступают в силу только в момент разадресации этого указателя, но не раньше. Это именно то требование, которое ты нарушил в своем примере выше.


Андрей, я признаю, что по букве закона вы полностью правы.

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

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

В данном конкретном случае я начал возражать потому, что ты пытаешься выдать метод C::print за метод класса B. Оставим на время то, что через static_cast это разрешено по стандарту. Проблема в том, что функция call получает указатель на метод класса B. И она вправе применить этот указатель не только к тому объекту, который ей передали в качестве первого аргумента, но и к любому другому объекту типа B. Например, функция call может создать собственный объект типа B и через указатель попробует вызывать у него метод. Которого нет в B. Конечно, можно сейчас сказать, что это был пример, что функция call была сделана так, чтобы вызывать метод только у того объекта, который ей передан в качестве аргумента и т.д. Все это так. Но так же верно и то, что полученный через static_cast указатель может быть использован сам по себе. Он же физически не связан с объектом класса C для которого был получен. Моя позиция в том, что применив здесь static_cast мы заткнули рот компилятору, который пытался сказать нам, что в данном конкретном случае (не вообще) мы не правы.

E>>Ну и в догонку пример того, как static_cast (будучи, в определенной степени синонимом преобразования типов в стиле C) позволяет легко прострелить себе ногу еще в одном случае:


E>>
E>>char c = ...;
E>>unsigned int u = static_cast< unsigned int >( c );
E>>


E>>В этом легальном C++ коде мы получим проблемы, если тип char будет знаковым, а значение c — отрицательным. И это как раз тот случай, когда "Нельзя = не рекомендуется, т.к. опасно" лично для меня более важно, чем "Нельзя = запрещено спецификацией языка". Но это уже совсем другая история.


АТ>Не совсем понимаю, о каких "проблемах" идет речь. Если значение 'c' отрицательно, то сработает арифметика по модулю, который подчиняются беззнаковые типы, и переменная 'u' проинициализируется значенем 'UINT_MAX + c + 1'. 'static_cast' тут никаким боком совершенно ни при чем. Приведенный тобой код строго эквивалентен простому


АТ>
АТ>unsigned int u = c;
АТ>


АТ>т.е. никакого отношения к 'static_cast' происходящее не имеет вообще. Подводным камнем в этом примере является только зависящая от реализации знаковость или беззнаковость типа 'char'. При чем здесь 'static_cast'?


Просто при том, что большинство компиляторов выдают хотя бы предупреждение на код без static_cast. И если программист, по наивности или забывчивости не заметил, что пытается привести меньшее знаковое число к большему беззнаковому, то у него есть шанс заметить это по предупреждению компилятора. А вот со static_cast у него даже шансов таких не будет. А ведь значение u могло бы использоваться в качестве индекса в каком-нибудь массиве... Но это уже другая тема.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[2]: Указатель на функцию
От: CEMb  
Дата: 27.12.04 08:13
Оценка:
Здравствуйте, Аноним, Вы писали:
А>А немогли бы вы ответить на вопрос в топике (Начнем все сначала. Указатель на функцию) в чем там проблемы.
Проблема в том, что он кода так и не показал...

Я вот со всеми соглашусь.
С одной стороны метод это функция с неявным this, с другой стороны (сёдня с утреца как раз читал статейку с тут про ексепшны в явных/неявных деструкорах наследующих классов) часто возникают сложные моменты на, казалось бы, простяцких ситуациях...
Re[5]: Указатель на функцию
От: Eugene Kilachkoff Россия  
Дата: 27.12.04 09:12
Оценка:
Здравствуйте, korzhik, Вы писали:
K>Неожидал что за ночь тут развернётся такая жаркая дискуссия
K>Честно говоря удивлён как на ровном месте создалась конфликтная ситуация.
K>Не понимаю зачем закрывать тему?
K>Вот демагогию заканчивать надо, а обсуждение технических вопросов предлагаю продолжить.
K>Вдруг вы заблуждаетесь или мы вас не поняли.
Релакс, это просто тролль.
http://www.rsdn.ru/Forum/?mid=965254
Автор: А.Якубовский
Дата: 26.12.04
... << RSDN@Home 1.1.3 stable >>
Re[2]: Указатель на функцию
От: CreatorCray  
Дата: 26.06.07 02:42
Оценка:
Здравствуйте, А.Якубовский, Вы писали:

[Skipped]

"О Господи!" (С)

один раз в жизни мне такое понадобилось — когда надо было в из программы хардварные бряки на собственные методы делать.
разумеется указатель на обычную функцию. Никаких виртуалов и прочих заморочек — там уже начинается такой compiler specific что даже разбираться желания нету.
template <typename classType> const void *GetClassMethodAddress(classType clt)
{
    union
    {
        classType    _clt;
        const void    *ptr;
    };
    _clt = clt;
    return ptr;
}

Код не мой, где нарыт честно гря уже не помню. Есть большая вероятность что где то тут, на кывте...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[3]: Указатель на функцию
От: CreatorCray  
Дата: 26.06.07 02:42
Оценка:
Здравствуйте, eao197, Вы писали:

E>Результат работы под VC++ 7.1:

E>Под Borland C++ 5.6:
E>Под GNU C++ 3.3.3 (cygwin):

Intel C++ 9.1
Debug/Release
sizeof(vp): 4, sizeof(mp): 4
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.