Классы и типы методов в них... - страница 3

 
simpleton:

Не выдёргивается толкование. Сказано, что "void * - указатель на данные любого типа, и он приводится к указателю любого типа". С первой частью фразы всё нормально. А со второй - что такое "указатель любого типа"? Как построить фразу, если действительно нужно было бы утверждать, что некий указатель приводится к указателю не только на данные? Так и построить, как stringo это сделал, ведь - верно?


Простак(извини - как представился), в случае подобного выкрутаса на PL/1, была бы ошибка Protection Violation at ... 

Ну, не умеет ни один нормальный компилятор такую патологию разруливать. Извини еще раз.  

Виноват, С умеет. Деструктор  срабатывает. 

 
tara:


Простак(извини - как представился), в случае подобного выкрутаса на PL/1, была бы ошибка Protection Violation at ... 

Ну, не умеет ни один нормальный компилятор такую патологию разруливать. Извини еще раз.  

Виноват, С умеет. Деструктор  срабатывает. 

При чём тут PL/1?

Здесь никакая не патология, а небрежно составленное утверждение. Для C++ - ещё и неверное. Не приводится в C++ указатель на void к указателю на другой тип данных без всякого предупреждения. Более того, это трактуется как ошибка, а не как предупреждение.

В C нет деструкторов.

 
simpleton:

В C нет деструкторов.

Спасибо. 
 

simpleton:

stringo:
В C/C++ есть ещё применения для типа void. Например, void * - указатель на данные любого типа. Такой указатель приводится к указателю любого типа безо всякого предупреждения

Неправда.

C:

Компилятор ругается вполне определённым образом:

C++:

int main() {
        void *p(&p);   // p - указатель на void
        int *i(p);     // i - указатель "любого типа", в данном случае - указатель на int
        int (*f)()(p); // f - указатель "не менее любого типа", в данном случае - указатель на функцию
        return 0;
}
Компилятор также ругается вполне определённым образом в обоих случаях:

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

Для данных примеров, кстати, для C:

с точки зрения "взрослого" компилятора, переменная f НЕ является использованной, несмотря на то, что она инициализирована.

Для C++:

с точки зрения "взрослого" компилятора, и переменная i, и переменная f, - обе НЕ являются использованными, несмотря на то, что обе инициализированы.

Логика "использованности", по крайней мере, для встроенных типов, очень проста: если значение переменной НЕ прочитано, она НЕ использована.
Именно поэтому "взрослый" компилятор считает, что в обоих случаях переменная p ЯВЛЯЕТСЯ использованной.

У меня это тоже не компилируется:

error C2065: p: необъявленный идентификатор

 Вот это компилируется и работает:

void main()
 {
  typedef int (*pf)();
  void *p = nullptr;   // p - указатель на void
  int *i = (int*)p;    // i - указатель "любого типа", в данном случае - указатель на int
  int (*f)() = (pf)p;  // f - указатель "не менее любого типа", в данном случае - указатель на функцию
 }
// или так:
void main()
 {
  typedef int (*pf)();
  void *p = nullptr;  // p - указатель на void
  int *i((int*)p);    // i - указатель "любого типа", в данном случае - указатель на int
  int (*f)()((pf)p);  // f - указатель "не менее любого типа", в данном случае - указатель на функцию
 }

Так что, утверждение Славы верно. Указатель типа void* именно приводится к любому типу. Формально, конструктор не приводит к типу, а инициализирует. Если не предусмотрены шаблон или перегрузка конструктора для какого-либо типа, то это не значит, что нельзя привести тип. Это означает, что непредусмотренным типом инициализировать нельзя. Базовые типы указателей не обязаны уметь инициализировать себя через конструктор, чем попало.

Т.е. в Вашем примере отсутствует приведение типа.

 
Zhunko:

У меня это тоже не компилируется:

error C2065: p: необъявленный идентификатор 

Видимо, не компилируется строка:

void *p(&p);

Если это так, значит, использованный компилятор не соответствует стандарту C++.

В момент начала инициализации, то есть, когда когда за идентификатором p обнаружена открывающая круглая скобка, переменная уже существует, хоть ещё и не инициализирована, и - более того - для неё выделена память. Действительно, инициализируется значением некая область памяти, а не "воздух", поэтому, к моменту инициализации, память должна быть обязательно выделена. А раз память выделена, то имеется и вполне определённый её адрес, который и является адресом переменной. Соответственно, в выражении, используемом для инициализации, правомерно использовать адрес этой самой инициализируемой переменной. То же самое, кстати, относится и к C.

Касательно С++ и даже того же MQL4++. В конструкторе класса при инициализации объекта в инициализирующей части конструктора (между ":" и "{") точно так же и по той же причине правомерно использовать this, который является адресом - на момент использования this - ещё не инициализированного объекта класса. Например, для того же MQL4++:

#property strict

/******************************************************************************/
class A {
private:
  /******************************************************************************/
  A *a;

public:
  /******************************************************************************/
  A(): a(GetPointer(this)) {
  }

  /******************************************************************************/
  void fun() const {
    Print("a = ", a);
  }
};

/******************************************************************************/
void OnStart() {
  A a;

  a.fun();
}

В инициализирующей части конструктора был использован this ещё неинициализированного объекта. Тем не менее, данный пример прекрасно компилируется без всяких предупреждений и прекрасно работает:

16:58:23 Script 3 EURUSDm,H1: loaded successfully
16:58:23 3 EURUSDm,H1: initialized
16:58:23 3 EURUSDm,H1: a = 1
16:58:23 3 EURUSDm,H1: uninit reason 0
16:58:23 Script 3 EURUSDm,H1: removed


Zhunko:

 Вот это компилируется и работает:

void main()
 {
  typedef int (*pf)();
  void *p = nullptr;   // p - указатель на void
  int *i = (int*)p;    // i - указатель "любого типа", в данном случае - указатель на int
  int (*f)() = (pf)p;  // f - указатель "не менее любого типа", в данном случае - указатель на функцию
 }
// или так:
void main()
 {
  typedef int (*pf)();
  void *p = nullptr;  // p - указатель на void
  int *i((int*)p);    // i - указатель "любого типа", в данном случае - указатель на int
  int (*f)()((pf)p);  // f - указатель "не менее любого типа", в данном случае - указатель на функцию
 }
 
Здесь применяется явное приведение, а существует, ведь, ещё и неявное. Кстати, прямая инициализация (вторая часть, помеченная комментарием "или так") для первого указателя выглядит следующим образом:
  void *p(nullptr);  // p - указатель на void


Zhunko:

Так что, утверждение Славы верно. Указатель типа void* именно приводится к любому типу.

Утверждение звучит следующим образом: "Например, void * - указатель на данные любого типа. Такой указатель приводится к указателю любого типа безо всякого предупреждения".

В утверждении не сказано, какого типа приведение имеется ввиду, явное или неявное. Однако, в утверждении имеется дополнение: "безо всякого предупреждения". Очевидно, что это позволяет вполне обоснованно предполагать, что имелось ввиду именно неявное приведение. В самом деле, возьмём тот же MQL4++:

#property strict

/******************************************************************************/
void OnStart() {
  long l = 5;
  int i = l;       /* line 6 */
//      ^ position 9
  int j = (int)l;  /* line 7 */
}

Компилятор совершенно определённо предупреждает о возможных последствиях при неявном приведении и совсем НЕ предупреждает при явном:

possible loss of data due to type conversion    3.mq4   6       9

Точно так же в смысле предупреждений при неявном приведении ведут себя и компиляторы C/C++.

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

Если нет явного приведения, это ещё не значит, что приведения нет вообще.


Zhunko:

Формально, конструктор не приводит к типу, а инициализирует. Если не предусмотрены шаблон или перегрузка конструктора для какого-либо типа, то это не значит, что нельзя привести тип. Это означает, что непредусмотренным типом инициализировать нельзя. Базовые типы указателей не обязаны уметь инициализировать себя через конструктор, чем попало.

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

Zhunko:

Т.е. в Вашем примере отсутствует приведение типа.

А как же тогда быть с тем, что компилятор для моего примера выдаёт вполне однозначные сообщения об ошибках? Вот эти:

try.cpp:3:10: error: invalid conversion from 'void*' to 'int*' [-fpermissive]
try.cpp:4:14: error: invalid conversion from 'void*' to 'int (*)()' [-fpermissive]

Одно из двух: или компилятор нагло врёт, ведя речь о какой-то там "conversion" между парой типов 'void*' и 'int*' и парой 'void*' и 'int (*)()', или кто-то ошибся в своём утверждении об отсутствии приведения типа в моём примере.

Скорее всего, кроме небрежности с "любым типом указателя", stringo либо просто ошибся "в направлении приведения" для C++ (это к указателю на void указатель на другой тип данных приводится неявно и без предупреждений) либо забыл, что в C++ с приведениями - строже, чем в C, и неявное приведение там допускается только к указателю на void (в одну сторону, а не в обе).

 
simpleton:

Видимо, не компилируется строка:

1. Если это так, значит, использованный компилятор не соответствует стандарту C++.

2. В момент начала инициализации, то есть, когда когда за идентификатором p обнаружена открывающая круглая скобка, переменная уже существует, хоть ещё и не инициализирована, и - более того - для неё выделена память. Действительно, инициализируется значением некая область памяти, а не "воздух", поэтому, к моменту инициализации, память должна быть обязательно выделена. А раз память выделена, то имеется и вполне определённый её адрес, который и является адресом переменной. Соответственно, в выражении, используемом для инициализации, правомерно использовать адрес этой самой инициализируемой переменной. То же самое, кстати, относится и к C.

3. В утверждении не сказано, какого типа приведение имеется ввиду, явное или неявное. Однако, в утверждении имеется дополнение: "безо всякого предупреждения". Очевидно, что это позволяет вполне обоснованно предполагать, что имелось ввиду именно неявное приведение. В самом деле, возьмём тот же MQL4++:

Компилятор совершенно определённо предупреждает о возможных последствиях при неявном приведении и совсем НЕ предупреждает при явном:

Точно так же в смысле предупреждений при неявном приведении ведут себя и компиляторы C/C++.

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

Если нет явного приведения, это ещё не значит, что приведения нет вообще.


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

5. А как же тогда быть с тем, что компилятор для моего примера выдаёт вполне однозначные сообщения об ошибках? Вот эти:

Одно из двух: или компилятор нагло врёт, ведя речь о какой-то там "conversion" между парой типов 'void*' и 'int*' и парой 'void*' и 'int (*)()', или кто-то ошибся в своём утверждении об отсутствии приведения типа в моём примере.

6. Скорее всего, кроме небрежности с "любым типом указателя", stringo либо просто ошибся "в направлении приведения" для C++ (это к указателю на void указатель на другой тип данных приводится неявно и без предупреждений) либо забыл, что в C++ с приведениями - строже, чем в C, и неявное приведение там допускается только к указателю на void (в одну сторону, а не в обе).

1. Да, Майкрософт всегда снабжает студию компилятором несоответствующим стандарту С++ :-)) Это обычное дело :-))

2. Компилятор видит переменную, как объявленную, сразу после завершения оператора. Т.е. после ";". Так всегда было.

3. Использование неявного приведения это действие на авось. Вдруг прокатит. Вот у вас в примере не прокатило. Не нашлось подходящего конструктора.

4. Как это, нет конструктора? Если пример ниже компилируется, то конструктор есть.

void main()
 {
  typedef int (*pf)();
  void *p = nullptr;  // p - указатель на void
  int *i((int*)p);    // i - указатель "любого типа", в данном случае - указатель на int
  int (*f)()((pf)p);  // f - указатель "не менее любого типа", в данном случае - указатель на функцию
 }

Все базовые типы имеют конструкторы.

5. У Вас какой-то "левый" компилятор. Или, возможно, сильно устаревший. У меня так:

1>(416): error C2440: инициализация: невозможно преобразовать "void *" в "int *"
1> Для преобразования "void*" к указателю на тип, не являющемуся "void", требуется явное приведение
1>(417): error C2440: инициализация: невозможно преобразовать "void *" в "int (__cdecl *)(void)"
1> Для преобразования "void*" к указателю на тип, не являющемуся "void", требуется явное приведение

Компилятор обнаружил в единственном конструкторе невозможное присваивание. Всё разжевал и сразу предложил сделать явное приведение.

6. Слава всё правильно написал. Суть, повторюсь, в том, что у Вас нет приведения (явного приведения). Неявное можете сами сделать с помощью обёртки для базового типа. Дополните ещё одним конструктором.

 
Zhunko:

1. Да, Майкрософт всегда снабжает студию компилятором несоответствующим стандарту С++ :-)) Это обычное дело :-))

2. Компилятор видит переменную, как объявленную, сразу после завершения оператора. Т.е. после ";". Так всегда было.

3. Использование неявного приведения это действие на авось. Вдруг прокатит. Вот у вас в примере не прокатило. Не нашлось подходящего конструктора.

4. Как это, нет конструктора? Если пример ниже компилируется, то конструктор есть.

Все базовые типы имеют конструкторы.

5. У Вас какой-то "левый" компилятор. Или, возможно, сильно устаревший.

1. Да, это так, но ничего смешного в этом не вижу. Правда, последнее время они начали исправляться, что радует: C++11/14 Core Language Features in VS 2013 and the Nov 2013 CTP.

2. Хоть какие-нибудь аргументы можно увидеть, кроме "так всегда было"?
И заодно объяснить следующее явление, нагло имеющее место не только в C++, но даже в MQL4++:

#property strict

/******************************************************************************/
void OnStart() {
  int i = 7, j = i + 1;
  Print("i = ", i, ", j = ", j);
}

Которое имеет наглость ещё и работать, как ожидается:

00:39:31 Script 3 EURUSDm,H1: loaded successfully
00:39:31 3 EURUSDm,H1: initialized
00:39:31 3 EURUSDm,H1: i = 7, j = 8
00:39:31 3 EURUSDm,H1: uninit reason 0
00:39:31 Script 3 EURUSDm,H1: removed

Как хорошо видно, в конструкции "int i = 7, j = i + 1;" для инициализации переменной j в выражении используется даже не адрес, а значение переменной i, хотя до "завершения оператора", то есть, до ";", дело ещё не дошло. Как это сопоставить с "так всегда было"?

Что ж, наверное следует обратиться к первоисточнику и, без преувеличения, - истине в последней инстанции, то есть, к стандарту C++11 (использован текущий драфт N3690):

3.3.2 Point of declaration                                                              [basic.scope.pdecl]

1 The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its
initializer (if any), except as noted below. [ Example:
  int x = 12;
  { int x = x; }

  Here the second x is initialized with its own (indeterminate) value. — end example ]

Совершенно ясно сказано, что точка декларации находится сразу после полного декларатора и до его инициализации, то есть, в моём примере, - на открывающей круглой скобке после имени переменной, как я и говорил. Более того, в только что процитированной мной части стандарта приведён пример, очень близкий к нашему случаю, (во внутреннем блоке, образованном { }) и далее прямо написано, что для этого случая переменная инициализируется своим собственным же (неопределённым) значением. Если уж прямо в стандарте пример приведён, - тут уж никуда не деться...

3. Это не у меня в примере "не прокатило", а утверждение stringo оказалось неверным, мой пример лишь был специально так составлен, чтобы это показать.

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

5. gcc version 4.7.2. Не настолько и устаревший.

Zhunko:

У меня так:

1>(416): error C2440: инициализация: невозможно преобразовать "void *" в "int *"
1> Для преобразования "void*" к указателю на тип, не являющемуся "void", требуется явное приведение
1>(417): error C2440: инициализация: невозможно преобразовать "void *" в "int (__cdecl *)(void)"
1> Для преобразования "void*" к указателю на тип, не являющемуся "void", требуется явное приведение

Компилятор обнаружил в единственном конструкторе невозможное присваивание. Всё разжевал и сразу предложил сделать явное приведение.

6. Слава всё правильно написал. Суть, повторюсь, в том, что у Вас нет приведения (явного приведения). Неявное можете сами сделать с помощью обёртки для базового типа. Дополните ещё одним конструктором.

Нет там никакого присваивания, там - инициализация, это сильно разные вещи. Более того, в сообщении об ошибках компилятор об этом прямым текстом и заявляет, цитирую: "error C2440: инициализация:". Инициализация, а не "невозможное присваивание". В сообщениях об ошибках также сказано о том, что, цитирую: "невозможно преобразовать" и "требуется явное приведение". Ничего об обнаружении "невозможного присваивания" там не сказано. И про конструкторы ни слова. Зачем же выдумывать?

6. Я специально привёл пример, чтобы показать, что stringo ошибся. Да, суть в том, что в C++ указатель на void НЕ приводится НЕЯВНО к указателю на тип данных, отличных от типа void. Вот, в обратную сторону - приводится НЕЯВНО, а так, как утверждал stringo - нет.

 
simpleton:

1. Да, это так, но ничего смешного в этом не вижу. Правда, последнее время они начали исправляться, что радует: C++11/14 Core Language Features in VS 2013 and the Nov 2013 CTP.

2. Хоть какие-нибудь аргументы можно увидеть, кроме "так всегда было"?
И заодно объяснить следующее явление, нагло имеющее место не только в C++, но даже в MQL4++:

Которое имеет наглость ещё и работать, как ожидается:

Как хорошо видно, в конструкции "int i = 7, j = i + 1;" для инициализации переменной j в выражении используется даже не адрес, а значение переменной i, хотя до "завершения оператора", то есть, до ";", дело ещё не дошло. Как же так, ведь "так всегда было"? Неувязочка...

Что ж, наверное следует обратиться к первоисточнику и, без преувеличения, - истине в последней инстанции, то есть, к стандарту C++11 (использован текущий драфт N3690):

Совершенно ясно сказано, что точка декларации находится сразу после полного декларатора и до его инициализации, то есть, в моём примере, - на открывающей круглой скобке после имени переменной, как я и говорил. Более того, в только что процитированной мной части стандарта приведён пример, очень близкий к нашему случаю, (во внутреннем блоке, образованном { }) и далее прямо написано, что для этого случая переменная инициализируется своим собственным же (неопределённым) значением. Если уж прямо в стандарте пример приведён, - тут уж никуда не деться...

3. Это не у меня в примере "не прокатило", а утверждение stringo оказалось неверным, мой пример лишь был специально так составлен, чтобы это показать.

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

5. gcc version 4.7.2. Не настолько и устаревший.

Нет там никакого присваивания, там - инициализация, это сильно разные вещи. Более того, в сообщении об ошибках компилятор об этом прямым текстом и заявляет, цитирую: "error C2440: инициализация:". Инициализация, а не "невозможное присваивание".
В сообщениях об ошибках также сказано о том, что, цитирую: "невозможно преобразовать" и "требуется явное приведение". Ничего об обнаружении "невозможного присваивания" там не сказано. И про конструкторы ни слова. Зачем же выдумывать?

6. Я специально привёл пример, чтобы показать, что stringo ошибся. Да, суть в том, что в C++ указатель на тип данных, отличных от типа void, НЕ приводится НЕЯВНО к указателю на void. Вот, в обратную сторону - приводится НЕЯВНО, а так, как утверждал stringo - нет.


Вы сами-то поняли, что сказали? 
 
Он прав. 
 
tara:

Вы сами-то поняли, что сказали? 
Да, понял. Единственное, - в заключительной части перепутал, когда формулировал. Но уже поправил.
Причина обращения: