Добрый день, Сообщество!
Читал я на досуге Алена Голуба
"ВЕРЕВКА ДОСТАТОЧНОЙ ДЛИНЫ, ЧТОБЫ… ВЫСТРЕЛИТЬ СЕБЕ В НОГУ
Правила программирования на Си и Си++"
Не согласен со следующими правилами
137. Виртуальная функция не является виртуальной, если вызывается из конструктора или деструктора
Ну на самом деле(насколько мне известно
) при вызове виртуальных функций класса из нутри класса не используется механиз виртуализации за исключением случая когда функция не переопределена в потомке, кроме того использование механизма виртуализации не обязательно при вызове функций объекта созданного на стеке при обращении к нему по имени, а не указатель на базовый класс.
138. Не вызывайте чисто виртуальные функции из конструкторов
Это правило вытекает из только что рассмотренной картины. Определение "чисто" виртуальной функции (у которой =0 вместо тела) приводит к тому, что в таблицу виртуальных функций базового класса помещается NULL вместо обычного указателя на функцию. (В случае "чисто" виртуальной функции нет функции, на которую необходимо указывать). Если вы вызываете чисто виртуальную функцию из конструктора, то используете таблицу базового класса и на самом деле вызываете функцию при помощи указателя NULL. Вы получите дамп оперативной памяти на машине с UNIX и "Общая ошибка защиты" в системе Windows, но MS-DOS просто исполнит то, что вы просили, и попытается выполнить код по адресу 0, считая его правильным.
Ха
. Кто сказал что у чисто виртуальной функции не может быть тела?
Вызывайте смело, если есть тело функции.
В подтверждение вот код:
class Base
{
public:
Base();
void virtual print() = 0;
};
Base::Base()
{
print();
}
void Base::print()
{
std::cout << "Base::print()\n";
}
class Deliver:public Base
{
public:
Deliver(){print();}
void virtual print();
};
void Deliver::print()
{
std::cout << "Deliver::print()\n";
}
int _tmain(int argc, _TCHAR* argv[])
{
Deliver d;
d.print();
dynamic_cast<Deliver*>(&d)->print();
dynamic_cast<Base*>(&d)->print();
return 0;
}
и ассемблерный код:
d.print();
00412816 lea ecx,[d]
00412819 call Deliver::print (411023h)
dynamic_cast<Deliver*>(&d)->print();
0041281E lea ecx,[d]
00412821 call Deliver::print (411023h)
dynamic_cast<Base*>(&d)->print();
00412826 mov esi,esp
00412828 mov eax,dword ptr [d]
0041282B lea ecx,[d]
0041282E call dword ptr [eax]
00412830 cmp esi,esp
00412832 call @ILT+235(__RTC_CheckEsp) (4110F0h)
return 0;
00412837 xor eax,eax
далее
145. Операция — это сокращение (без сюрпризов)
.........
.........
.........
Другим хорошим примером того, как нельзя действовать, является интерфейс Си++ iostream. Использование сдвига (<<) для обозначения "вывод" является нелепым. Ваши функции вывода в Си назывались printf(), а не shiftf(). Я понимаю, что Страуструп выбрал сдвиг, потому что он сходен с механизмом перенаправления ввода/вывода различных оболочек UNIX, но этот довод на самом деле не выдерживает проверки. Страуструп исходил из того, что все программисты на Си++ понимают перенаправление в стиле UNIX, но эта концепция отсутствует в некоторых операционных системах — например, в Microsoft Windows.
.........
.........
.........
Последнее предложение ставит меня в тупик. Я довольно часто по виндой использую перенаправление потоков и pipe при работе в командной строке, МС ДОС помниться тоже поддерживал эту штуку.
.........
К тому же, для того, чтобы аналогия была полной, операция > должна быть перегружена для выполнения операции затирания, а >> — добавления в конец. Тем не менее, тот факт, что > и >> имеют различный приоритет, делает реализацию такого поведения затруднительной. Дело осложняется тем, что операторы сдвига имеют неправильный уровень приоритета. Оператор типа cout << x += 1 не будет работать так, как вы ожидаете, потому что у << более высокий приоритет, чем у +=, поэтому оператор интерпретируется как (cout << x) += 1, что неверно. Си++ нуждается в расширяемости, обеспечиваемой системой iostream, но он вынужден добиваться ее за счет введения операторов "ввода" и "вывода", имеющих низший приоритет по отношению к любому оператору языка.
.........
Ну так-то оно так, но будет ошибка компиляции, что не так страшно обернул скобками и работай дальше.
.........
Аналогия проблеме "сдвиг как вывод" может быть найдена в проектировании компьютерных систем. Большинство проектировщиков аппаратуры были бы счастливы использовать + вместо OR, а * вместо AND, потому что такая запись используется во многих системах проектирования электронных компонентов. Несмотря на это, перегрузка операции operator+() в качестве OR явно не нужна в Си++. К тому же, лексема << означает "сдвиг" в Си и Си++; она не означает "вывод".
.........
лично для мне перегруженные << и >> интуитивно понятны.
Как завершающий пример этой проблемы — я иногда видел реализации класса "множество", определяющие | и & со значениями "объединение" и "пересечение". Это может иметь смысл для математика, знакомого с таким стилем записи, но при этом не является выражением ни Си, ни Си++, поэтому будет незнакомо для вашего среднего программиста на Си++ (и вследствие этого с трудом сопровождаться). Амперсанд является сокращением для AND; вы не должны назначать ему произвольное значение. Нет абсолютно ничего плохого в a.Union(b) или a.intersect(b). (Вы не можете использовать a.union(b) со строчной буквой u, потому что union является ключевым словом).
выше капец, то не используй это не используй, лично меня перегруженные операторы & в BOOST::SERIALIZE вполне устраивают. Кроме того перегрузка поператор даёт возможность написать
ar & a & b & c;
а не городить огород из
ar.Union(a);
ar.Union(b);
ar.Union(c);
особенно это относится к <<
Вот высказал своё ИМХО, критикуйте