Забыл упомянуть о способе борьбы с неожиданными перераспределениями памяти в std::vector. Есть метод std::vector::reserve который позвляет зарезервировать необходимое количество памяти заранее, например если известно примерное число элементов, которые будут храниться в векторе:
Метод std::vector::capacity позволяет понять сколько элементов можно положить в вектор без перераспределения памяти.
Об использовании алгоритмов
На самом деле, использование STL (и им подобных) алгоритмов кажется через чур запутанным или, возможно неуместным, не всем новичкам в C++. Тут все дело в менталитете — для тех кто переходит на C++ с C, Pascal или Java обобщенное программирование может показаться трюком «для гуру» который только нарушает читаемость программ. Для тех же, кто имеет опыт в функциональных языках типа LISP или Scheme, или, хотя бы, Python или Ruby, возможности обобщенного программирования в C++ могут показаться весьма скудными.
NVI Idiom и «стандартная» практика программирования
Мне был задан вопрос: откуда свалилась это правило использовать невиртуальные функции в интерфейсе класса, а виртуальные только в закрытых и защищенных секциях и что, например в Java, давно существует подход определенно другой. Я про Java много что наговорил, кроме главного. Главное в том, что независимо от того насколько плох или хорош Java или C++, имея много общего, во многом они различаются по стилю программирования (т.е. идиоматически). Во многих случаях нельзя прямо руководствоваться своим опытом программирования на Java в C++ (и наоброт) и это должно быть очевидно: в Java нет деструкторов, есть сборка мусора и все классы автоматически наследуют Object. Это и многое другое вносит существенные коррективы.
Поэтому существуют, скажем ОО паттерны (общие для многих ОО языков программирования) и идиомы, специфичные к одному.
Что касается NVI – то это скорее идиома, однако существует родственный паттерн — Template Method, который успешно применяется в Java.
Было несколько вопросов и реплик относительно безопасности программ, безопасности языков программирования и т.п. Я бы хотел высказать свое мнение на свежую голову, при этом никоим образом не пытаюсь навязать его как авторитетное.
Итак,
Качественное ПО – это то ПО, которое удовлетворяет поставленным требованиям. И в конечном итоге удовлетворяет пользователя.
Надежное ПО – это программы, которые мало подвержены к отказам. Т.е. программа ведет себя предсказуемо и валидно (согласно требованиям) даже в случаях, когда переданы неверные данные на вход, нехватает ресурсов и т.п.
Есть также понятие времени наработки на отказ, у надежного ПО оно должно быть достаточно (или очень) велико.
Под безопасным кодом я подразумеваю прежде всего код, продуктом которого является надежное ПО.
Все это я написал не заглядывая в словари, спец литературу и т.п., поэтому прошу меня исправить, если что.
Теперь о безопасности языков программирования. Далее приводится цитата из книги <<Дизайн и эволюция языка C + +>> (раздел 14.2.4), которая мне кажется кое что может прояснить:
Хорошие программы – это продукт хорошего образования, удачного проектирования, адекватного тестирования и т.д., а не наличие в языке средств, которые предположительно должны использоваться только «правильно». Употребить во вред можно любое средство, поэтому вопрос не в том, можно ли чем-то воспользоваться неправильно (можно!) или будут ли этим неправильно пользоваться (будут!). Вопрос в том, необходимо ли данное средство при условии его правильного употребления настолько, чтобы оправдать затраты на его реализацию, удастся ли смоделировать его с помощью других средств языка и реально ли с помощью обучения обратить во благо неправильное использование.
3 Окт 2006
Вопрос про строковый литерал
Вопрос: Что является константным в выражении const char str[] = String literal; массив или char? Ответ: Конечно char.
Вот текст из Страуструпа:
The type of a string literal is array of the appropriate number of cons characters, so Bohr is of type const char [5].
A string literal can be assigned to a char*. This is allowed because in previous definitions of C and C + +, the type of a string literal was char*. Allowing the assignment of a string literal to a char* ensures that millions of lines of C and C++ remain valid.
Вопрос про ошибку компиляции (личный)
Сев в автобус, я вдруг понял, что совершенно зря пообещал побить костылем одного господина. Дело в том, что вопрос был совершенно законным. Дело в порядке поиска имен. Компилятор ищет функцию, которую следует вызвать в каждом конкретном случае, среди доступных областей видимости в определенном порядке. Область видимости имен класса является одним из первых кандидатов для поиска. Когда компилятор находит функцию (или несколько) с соответствующим именем (независимо от параметров), поиск прекращается.
В конкретном случае, класс, по всей видимости, содержал функцию f(int*) с искомым именем, но другими параметрами. Не найдя других кандидатов внутри класса, компилятор остановил поиск и сообщил об ошибке.
Про строковый литерал: да, cпасибо. Я потом нашел это место в стандарте (2.13.4.1): An ordinary string literal has type “array of n const char” and static storage duration, where n is the size of the string
На лекции был затронут еще один интересный вопрос, отчасти связанный с этим: что первично в декларации – префикс или постфикс? Я предполагал, что постфикс, основываясь на известном эмпирическом правиле – go right, then go left Оно задаёт простой алгоритм понимания достаточно сложных объявлений, вроде «функция, которая возвращает указатель на массив из десяти функций, которые возвращают int» Но на лекции утверждалось, что первичен префикс. И тоже была некоторая логика, которую я вроде бы сначала уловил, но сейчас уже воспроизвести не смогу.
P.P.S. Про вопрос об ошибке компиляции: так ведь нет в классе функции с похожей сигнатурой – в том-то и дело. Похоже что и вправду темна вода в облацех. Впрочем, вопрос был исключительно теоретический, и в стремлении побить меня костылём Вы были правы :)
Да, про постфикс вы правы. Поскольку материал для первой части курса берется из Страуструпа, приведу еще пример (4.9.1):
The postfix declarator operators bind tighter than the prefix ones. Consequently, *kings[] is a vector of pointers to something, and we have to use parentheses to express types such as pointer to function;"
Про gcc-4 интересно – надо попробовать. Сможете тестовый пример сделать? :)
Например, вот (сразу отмечу, что автор этого кода – не я, меня не интересует. как в этой ситуации получить компилирующийся код, мне здесь интересна только логика компилятора)
class C
{
void fun(A::B *c);
};
class A
{
public:
class B{};
};
void C::fun(A::B *z)
{
}
int main() {}
Вот ошибки gcc-4,0:
f.cpp:3: error: ‘A’ has not been declared
f.cpp:3: error: ‘B’ has not been declared
f.cpp:12: error: prototype for ‘void C::fun(A::B*)’ does not match any in class ‘C’
f.cpp:3: error: candidate is: void C::fun(int*)
Дык эта, все правильно. Во всяком случае, логично.
GCC все типы, определения которых он не понял, считает int. Так еще GCC 1.х было.
То есть он прототип в классе C воспринял как void fun(int *), потому что не понял, что такое A и B.