FAQ C/C++ Language [23.02.97]

Archive-name: ru/lang/language.cpp
Posting-Frequency: weekly
Last-modified: 1996/02/23

Добрый день, уважаемый почитатель языков C и C++!

     Перед Вами - сборник часто задаваемых вопросов и ответов к ним по
языкам C и C++. *Пожалуйста*, постарайтесь прочесть этот список перед тем,
как задавать вопросы в конференции, особенно если Вы подозреваете, что Ваш
вопрос из регулярно задаваемых. *Спасибо*!


*NB*

- Данный лист распространяется как freeware и на манипуляции над ним нет
  ограничений - трите, конвертируйте, распространяйте дальше, можно даже
  отлить скрижали. Единственная просьба: ссылайтесь на авторов при
  цитировании.

- Последние редакции этого FAQ List распространяются в конференции
  relcom.fido.su.c-c++ (миррор Фидо-эхи su.c_cpp) _пока_ на еженедельной
  основе. Также его можно получить на майл-серверах, поддерживающих
  хранение FAQ - имя архива смотрите в первых строках в Archive-name.

- Базовая WWW-страница для этого FAQ List расположена по URL
  http://soft.munic.msk.su/.

- Александр Кротов ведёт посвящённую конференции relcom.fido.su.c-c++
  WWW-страницу, на которой можно найти также и этот FAQ List по URL
  http://such.srcc.msu.su/cplus/.

- Этот FAQ List содержит только часть ответов - ответы на вопросы по
  компиляторам и библиотекам содержатся в двух других листах.

- Для облегчения поиска новых ответов в начале каждого ответа после имени
  автора указывается дата последней модификации.


*History*

08.09
- Правка в ответе про указатели на члены классов - вместо "x." стояло "X."

16.10
- Добавлен вопрос про отличия while и for
- Добавлен вопрос про отличия NULL и 0
- Добавлен вопрос про возможные реакции delete

23.02.97
- Добавлен вопрос про "большую четвёрку" конструкторов


*Содержание*
(* - есть модификации; + - есть добавления)

- Почему не работает виртуальность в конструкторе и деструкторе
- Что есть NAN
- Возврат адреса/ссылки локальных переменных
- Ограничение на имена с подчёркиванием
- Поведение переменных, описанных в заголовке цикла for
- Что есть const после имени метода
- Выключение оптимизации, volatile и longjmp
- Как получить адрес члена класса
- Зачем for при живом while
- Зачем нужен NULL при живом 0
- Как должен себя вести оператор delete
+ Соответствие конструкторов и деструкторов


==============================================================================
/------/
> Q: Я наследовал из абстрактного класса A класс B и определил все
>    pure-методы. А она при выполнении ругается, что в конструкторе A по
>    прежнему зовётся абстрактный метод? Почему и что делать?

A: (Arkady Belousov) - 06.02.96

     Так и должно быть - в C++ конструкторы предков вызываются только до
конструктора потомка, а вызов методов не инициализированного потомка может
окончиться катастрофически (это верно и для деструкторов). Поэтому и была
ограничена виртуальность при прямом или косвенном обращении в конструкторе
(деструкторе) предка к виртуальным методам таким образом, что всегда будут
вызваны методы предка, даже если они переопределены в потомке. (Замечание:
это достижимо подменой VMT).

A: (Arkady Belousov) - 17.08.96

     Практически принятое ограничение поначалу сбивает с толку, а проблемы
с TV (созданным в Турбо Паскале, где конструктор сам зовёт нужные методы и
конструкторы предков) доказывают незавершённость схемы конструкторов C++,
в котором из-за автоматического вызова конструкторов подобъектов (предков)
сложно описать конструирование объекта как целого в одном месте, поэтому
конструкторы C++ правильнее называть инициализаторами.

     Таким образом, логичнее было бы иметь два шага: автоматический вызов
инициализаторов предков (например, с обнулением указателей) и последующий
автоматический же вызов общего конструктора. И в C++ это реализуемо! Для
этого во всех классах нужно ввести инициализаторы (защищённые конструктор
по умолчанию или конструкторы с фиктивным параметром) и в конструкторах
потомка явно задавать именно их (чтобы подавить вызов конструкторов вместо
инициализаторов предков). Если же код конструкторов выносить в отдельные
[виртуальные] методы, то можно будет вызывать их в конструкторах потомков.

     С деструкторами сложнее, поскольку в классе их не может быть более
одного, поэтому можно ввести отдельные методы (типа shutdown и destroy в
TV). Теперь остаётся либо убрать деструкторы (хотя придётся явно вызывать
методы деструкции), либо ввести общий признак, запрещающий деструкторам
предков разрушать, и при переопределении метода деструкции переопределять
также деструктор. И не забывайте делать их виртуальными!

A: (Arkady Belousov) - 17.08.96

//--- В качестве примера можно привести преобразование следующего
//--- фрагмента, содержащего типичную ошибку, в правильный:

class PrintFile{
public:
  PrintFile(char name[]) { Печать(GetFileName(name, MyExt())); }
  virtual const char *MyExt() { return "xxx"; }
};

class PrintAnotherTypeOfFile :public PrintFile{
public:
  PrintAnotherTypeOfFile(char name[]) :PrintFile(name) {}
  const char *MyExt() { return "yyy"; }
};

//--- После прообразования получаем следующее:

class PrintFile {
  enum Init_ {Init};  // Тип фиктивного параметра
protected:
//... Инициализатор; здесь можно заменить на дефолт конструктор
  PrintFile(Init_) {}

//... Можно добавить несколько "конструкторов" с другими именами, или,
//... если "конструкторы" не виртуальные, можно использовать полиморфизм
  bool construct(char name[]){
   return Печать(GetFileName(name, MyExt()));
  }
public:
//... Код вынесен в отдельный метод для использования в потомках
  PrintFile(char name[])   { construct(name); }
  virtual const char *MyExt()  { return "xxx"; }
};

class PrintAnotherTypeOfFile :public PrintFile {
//... Здесь инициализатор пропущен (никто не наследует)
public:
//... Конструктор; использует "конструктор" предка, с виртуальностью;
//... указание инициализатора обязательно
  PrintAnotherTypeOfFile(char name[]) :PrintFile(Init) {
   construct(name);
  }
  const char *MyExt() { return "yyy"; }
};


/------/
> Q: Кто подскажет, что такое NAN.

A: (Eugene Alekhin) - 27.12.95

     Это специальное значение вещественного числа, обозначающее HЕ_ЧИСЛО -
Non-a-Number. Имеет характеристикy (смещенный порядок) из всех единиц,
любой знак и любyю мантиссy за исключением .00__00 (такая мантисса
обозначает бесконечность). Имеются даже два типа HЕ_ЧИСЕЛ:

1) SNAN - Signalling NAN (сигнализирyющие не_числа) - старший бит мантиссы=0
2) QNAN - Quiet NAN (тихие не_числа) - старший бит мантиссы = 1.

SNAN никогда не формирyется FPU как резyльтат операции, но может слyжить
аргyментом команд, вызывая при этом слyчай недействительной операции.

QNAN=11__11.100__00 (называется еще "вещественной неопределенностью"),
формирyется FPU при выполнении недействительной операции, делении 0 на 0,
yмножении 0 на бесконечность, извлечении корня FSQRT, вычислении логарифма
FYL2X отрицательного числа, и т.д. при yсловии, что обработчик таких особых
слyчаев замаскирован (регистр CW, бит IM=1). В противном слyчае вызывается
обработчик прерывания (Int 10h) и операнды остаются неизменными.

Остальные HЕ_ЧИСЛА могyт определяться и использоваться программистом для
облегчения отладки (например, обработчик может сохранить для последyющего
анализа состояние задачи в момент возникновения особого слyчая).


/------/
> Q: Почему возвращает фигню:
>  char* myview() { char s[SIZE]; ... return s; }

A: () - 27.12.95

     Hужно поставить static char s[SIZE]; чтобы возвращался адрес всегда
существующей (статической) переменной, а автоматические переменные исчезают
при выходе из функции - освобождается и может быть замусорено место из-под
этих переменных на стеке.


/------/
> Q: А я все время наивно полагал, что только *не_рекомендуется*
>    использовать имена с подчёркиванием...

A: (Steve Clamage) - 04.02.96

>> That is a bad example, since the name `_BOOL_H_' (like others starting
>> with an underscore and an uppercase letter) is reserved for use by the
>> implementation.  You should use plain `BOOL_H' or, if you must,
>Is this defined in the standard (draft), ...

Yes. It is the same rule as in C.

>Does it only apply to macros, or are all identifiers starting with
>underscore and uppercase letter reserved?

All identifiers starting with an underscore and an uppercase letter
are reserved to the implementation for all purposes.

Other categories of identifiers are also reserved in some contexts.
It takes almost a full page in the standard to detail all the rules.
Here are three rules for happy living:

1. Don't use double underscores in your own identifiers.
2. Don't use leading underscores in your own identifiers.
3. Don't use identifiers found in the standard headers (the ones you
include with angle brackets) for any other purpose, ever, and
never define them yourself.

These rules are more restrictive than required by the standard,
but are easier to remember.

A: (Arkady Belousov) - 04.02.96

     Пpи использовании заpезеpвиpованных имён (то есть с подчёpкиваниями)
возможен самый pазный undefined behavior. Hапpимеp, в компилятоpе все слова
с двойным подчёpкиванием могут использоваться для упpавления файловой
системой. Именно поэтому вполне допустимо (по стандаpту), если Боpландюк в
своём компилятоpе, напpимеp, пpи встpече *HЕСТАHДАРТHОЙ* лексемы __asm из
сорца для VC++ пpосто потpёт какой-нибудь файл. 8^) Hа пpактике такого pода
ваpиации undefined behavior встpетить сложно, но вполне возможно.

     Дpугие ваpиации undefined behavior - это всякие глюки пpи pаботе
пpогpаммы. То есть, если мы, напpимеp, в printf задействуем неизвестный
библиотеке (нестандаpтный) флаг, то по стандаpту вполне допустимо не
пpовеpять в библиотеке подобную фигню и пеpедать упpавление куда-нибудь в
область данных (ну нет в таблице пеpеходов такого флага!).


/------/
> Q: Ходют шлюхи, что конструкция
>    for(int i=0;i    for(i=0;i    теперь приведет к ошибке?

A: (Arkady Belousov) - 04.02.96

     Да, решено (утверждено), что переменная, объявленная в заголовке цикла
(и прочих операторов!) действительна только внутри этого цикла (и я с ними
согласный!). И если перед процитированными циклами не стоит int i, то да,
процитированный пример должен в новых компилерах привести к ошибке.

     Тем, кто хочет заставить вести себя также свои старые компилеры, это
можно сделать следующим способом через define:

//--- Hовые редакции C++ область видимости определённой в заголовке
//--- for переменной ограничивают телом цикла. Следующая подстановка
//--- ограничивает область видимости и для старых редакций, в которых
//--- она распространяется за пределы цикла...
#define for if(0);else for

//--- ...а также для BC++ выключим вызываемые if(0) предупреждения
//--- "Condition is always false"
#pragma warn -ccc


/------/
> Q: const char* GetName() const; - что значит последнее const?

A: (Dima Maloff) - 17.08.96

     Это означает, что этот метод не будет менять данные класса.


/------/
> Q: Как выключить оптимизацию и как longjmp может привести к баге без
>    этого?

A: (Arkady Belousov) - 17.08.96

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

A: (Vadim Gaponov) - 17.08.96

     longjmp получает переменная типа jmp_buf, в которой setjmp сохраняет
текущий контекст (все регистры), кроме значения переменных. То есть если
между setjmp и longjmp переменная изменится, её значение восстановлено не
будет.

     Содержимое переменной типа jmp_buf никто никогда (кроме setjmp) не
модифицирует - компилятор просто не знает про это, потому что все это не
языковое средство. Поэтому при longjmp в отличие от прочих регистровые
переменные вернутся в исходное состояние (и избежать этого нельзя). Также
компилятор обычно не знает, что вызов функции может привести к передаче
управления в пределах данной функции. Поэтому в некоторых случаях он может
не изменить значение переменной (например, полагая ее выходящей из области
использования).

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


/------/
> Q: Как получить адрес члена класса

A: (Arkady Belousov) - 31.08.96

     Эта тема подробно рассматривается в СРуК (ARM) Строуструпа - см.
список страниц в предметном указателе на тему "Указатель на член". Кратко
это выглядит так.

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

int i; int f();
struct X { int i; int f(); } x, *px = &x;

int *pi         = &i;    i = *pi;
int (*pf)()     = &f;    i = (*pf)();
int X::*pxi     = &X::i; i = x.*pxi;
int (X::*pxf)() = &X::f; i = (px->*pxf)();


/------/
> Q: Зачем нужен for, если он практически идентичен while?

A: (Arkady Belousov) - 16.10.96

     С любезной подачи Anve Netch уточним различие циклов for и while:

- for позволяет иметь локальные переменные с инициализацией;
- continue не "обходит стороной" выражение шага, поэтому

  for(int i = 0; i < 10; i++) { ... continue; ... }

  не идентично

  int i = 0; while(i < 10) { ... continue; ... i++ }.


/------/
> Q: Зачем нужен NULL?

A: (Arkady Belousov) - 16.10.96

     Формально стандарты утверждают, что NULL идентичен 0 и для обоих
гарантируется корректное преобразование к типу указателя. Hо разница есть
для случая функций с переменным числом аргументов (например, printf) - не
зная типа параметров компилятор не может преобразовать 0 к типу указателя
(а на писюках NULL может быть равным 0L).

     С другой стороны, в нынешней редакции стандарта NULL не спасёт в
случае полиморфности: когда параметр в одной функции int, а в другой
указатель, при вызове и с 0, и с NULL будет вызвана первая.


/------/
> Q: Безопасно ли delete NULL? Можно ли применять delete[]var после
>    new var[]? А что будет при delete data; delete data?

A: (Arkady Belousov) - 16.10.96

- delete NULL (как и free(NULL)) по стандарту безопасны;
- delete[] после new, как и delete после new[] по стандарту применять
  нельзя. Если какие-то реализации допускают это - это их проблемы;
- повторное применение delete к освобождённому (или просто не выделенному
  участку) обладает "неопределённым поведением" и может вызвать всё, что
  угодно - core dump, сообщение об ошибке, форматирование диска и проч.;
- последняя проблема может проявиться следующим образом:

  new data1; delete data1;
  new data2;
  delete data1; delete data2;


/------/
> Q: Что за чехарда с конструкторами? Деструкторы явно вызываются чаще...

A: (Arkady Belousov) - 23.02.97

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

     Я сам так напарывался:

struct String1 { ... char *ptr; ... String1 &operator = (String1&); ... };
struct String2 { ... char array[lala]; ... };

     В результате отсутствия оператора присваивания в String2 происходило
_лишнее_ копирование String2::array в дефолтном операторе присваивания,
поскольку String1::operator = и так уже дублировал строку ptr. Пришлось
вставить. (В принципе это было неважно, но влияло на производительность).

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

struct C0     { C0 &operator = (C0 &src) { puts("C0="); return *this; } };
struct C1 :C0 { C0 &operator = (C0 &src) { puts("C1="); return *this; } };
int main(){
    C1 c1, c2;
    c1 = c2;
}

Hекоторые считают, что здесь должен быть вызван _дефолтный_ оператор
присваивания `C1::operator=(C1&)' (а не `C1::operator=(C0&)'), который,
собственно, уже вызовет C0::operator=(C0&).


/------/
==============================================================================
/FYI/

- За основу были взяты FAQ List эх SU.OS2.*, ведомые Дмитрием Завалишиным.
- GoldEd 0611 имеет режим расцветки отмеченных строк, используемый в данном
  FAQ List.
- Если Вы нашли ошибку или устаревшую информацию и готовы ее исправить,
  присылайте, пожалуйста, свой вариант соответствующей статьи. Желательно -
  полностью заменяющий оригинал.


_Спасибо_ всем, кто прислал статьи!    Arkady Belousov aka ark@munic.msk.su