Сейчас приходится собеседовать много новичков.
Заметил что ничего в жизни не меняется.
Даже люди с опытом работы в крупных наступают на те же грабли, на которые наступал я в своё время.
Хочу представить такие рекомендации новичкам в C++. Для того чтобы вы могли повысить свой уровень
ну и хотя бы лучше пройти собеседование.
Если кто желает что-то добавить — пожалуйста.
Только просьба придерживаться таких соглашений:
— Это должны быть грабли, на которые наступают регулярно.
— Краткое описание проблемы и методы её решение.
— Обяснение причин.
— Проверенный (скомпилированный) пример кода.
19.09.12 19:37: Перенесено из 'C/C++'
19.09.12 19:37: Перенесено из 'C/C++'
23.09.12 23:41: Перенесено из 'C/C++'
23.09.12 23:41: Перенесено из 'C/C++'
23.09.12 23:41: Перенесено из 'C/C++'
Re: Копирующий конструктор и копирующий operator= по умолчанию.
Легко ошибиться и выполнить присваивание или вызвать копирующий конструктор для такого объекта.
Вот такой код запросто скомпилируется и будет работать неправильно.
Т.е. мы присвоили указатель data объекта a объектам b и c.
В результате у нас 3 раза освободился один и тот же указатель, а data объекта c вообще потерялся.
Решение — сделать private копирующий конструктор и operator=. При этом достаточно их просто объявить, тело функций добавлять не надо.
Если вы создаёте объект, то для его разрушения должны вызывать delete.
Если вы создаёте массив, то для его разрушения должны вызывать delete[].
Иначе ваш код будет работать неправильно.
Для компилятора указатель на объект и указатель на массив неразличимы, поэтому это ваша проблема следить, какой delete вызвать.
item_t* v= new item_t;
...
delete v;
item_t* v= new item_t[100];
...
delete[] v;
Что будет, если для массивов вызвать обычный delete?
Либо осовободится только один объект, либо программа упадёт.
Можете сами в этом убедится, выполнив такой код:
Часто нужно присвоить потомку содержимое базового класса другого объекта.
Неявный operator=() потомка не работает, когда присваивается базовый класс.
Не надо почленно копировать объект, используйте static_cast.
#include <stdio.h>
#include <string>
struct human_t
{
std::string first_name;
std::string last_name;
unsigned birth_year;
unsigned birth_month;
unsigned birth_day;
unsigned birth_hour;
unsigned birth_min;
unsigned birth_sec;
human_t()
{
birth_year=birth_month=birth_day=
birth_hour=birth_min=birth_sec=0;
}
};
struct women_t : public human_t
{
std::string maiden_name;
};
int main(int argc,char** argv)
{
human_t h;
women_t w;
//w=h; такая строчка не компилируется
//часто встречается такой код
w.first_name=h.first_name;
w.last_name=h.last_name;
w.birth_year=h.birth_year;
w.birth_month=h.birth_month;
w.birth_day=h.birth_day;
w.birth_hour=h.birth_hour;
w.birth_min=h.birth_min;
w.birth_sec=h.birth_sec;
//можно прощеstatic_cast<human_t&>(w)=h;return 0;
}
Если вы планируете удалять производный класс, используя указатель на базовый класс, вы должны сделать дестурктор базового класса виртуальным.
Иначе деструктор потомка не будет вызван.
Это также касается случая, когда в вашем производном классе нет явного деструктора. В нём могут быть переменные (например, типа std::string) для которых нужно вызвать деструктор.
delete не может вызвать деструктор для тех объектов, чьё объявление классов он не видит.
В таком случае просто освободится память, занимаемая объектом.
Нет никаких причин их использовать.
Класс строк есть в STL, каждый фреймворк содержит свой класс строк.
По эффективности все они работают также, а иногда даже лучше, чем ваш собственный код на C строках.
В любой момент вы их сможете преобразовать к константной строке C.
STl — std::string,std::wstring
VCL — AnsiString,WideString
MFC — CString
Причины для этого такие:
— Вы не забудете освободить объект.
— Вы перестаете думать о переспределении памяти и расчёте необходимого количества памяти.
— Исчезает угроза переполнения буффера.
AnsiString,CString даже содержат в себе sprintf()
Вот такой код:
char q[1024];
strcpy(q, "select p.street from payers as p, pl_accounts as a where a.orgid=");
strcat(q, vOrgID.at(lsOrganisation.GetCurSel()).GetString());
strcat(q, " and p.id=a.payerid group by p.street order by p.street;");
if(!cur.Prepare(q))
Должен выглядеть так:
CString q="select p.street from payers as p, pl_accounts as a where a.orgid=";
q+=vOrgID.at(lsOrganisation.GetCurSel());
q+" and p.id=a.payerid group by p.street order by p.street;";
if(!cur.Prepare(q))
Если if(!cur.Prepare(q)) вдруг не соберётся, то if(!cur.Prepare(q.GetString()))
Почему-то довольно часто приходится слышать от C++ программистов, что они не используют стандартную библиотеку.
Согласитесь странно было бы слышать от программиста на JavaScript или PHP: "Я не использую строки" или "Я не использую массивы или ассоциативные массивы".
Зато каждый четвёртый кандидат на должность C++ программиста, никогда не слышал о STL.
Каждый второй кандидат на предложение отсортировать массив, предлагает сортировку пузырьком!!!
Только два кандидата, с помощью гугла, смогли дать более-менее рабочий код с использованием std::sort()
Между тем основное, что есть в библиотеке это контейнеры и алгоритмы.
Контейнеры:
std::string — строки
std::vector<> — массив
std::map<> — двоичное дерево или ассоциативный массив
std::set<> — множество. Фактически тот же std::map только хранит одни ключи, без значений.
std::hash_map<>,std::hash_set<> — тоже самое что и std::map<>,std::set<> — только не в бинарном дереве, а в хеш таблице.
Алгоритмы реализуют множество стандартных алгоритмов, знакомых нам с института. Например: поиск, поиск последовательности, двоичный поиск, сортировка, лексикографической сравнение, пересечение\объединение\вычитание множеств, уникалоьность.
Как использовать, на пальцах не объяснишь, придётся почитать книжку. хотя бы Бьёрн Страуструп "Язык C++". Часть "Стандартная библиотека"
Вот тестовое задание, которое вводит в ступор всех кандидатов:
#include <string>
#include <vector>
#include <algorithm>
struct human_t
{
std::string first_name;
std::string last_name;
unsigned age;
human_t()
{
age=0;
}
};
int main(int argc,char** argv)
{
std::vector<human_t> v;
return 0;
}
Отсортировать v по фамилии и имени.
Старайтесь избегать в коде явного освобождения ресурсов.
Причины для этого такие:
— Вы можете выйти из области видимости (например, return посреди функции), забыв освободить ресурс.
— В функции или в вызываемых функциях может произойти исключение.
Поэтому либо ресурс останется неосвобождённым, либо придётся расставлять try\catch после каждой инициализации ресурсов в функции.
//Пример автоматического вызова fclose()class file_hldr
{
file_hldr(const file_hldr&);
void operator=(const file_hldr&);
public:
FILE* f;
file_hldr(FILE* _f) {f=_f;}
~file_hldr(){if(f)fclose(f);}
};
//Использовать так:void foo()
{
FILE* f=fopen("data.txt","rb");
file_hldr fh(f);//С этого момента, чтобы не случилось для f будет вызван fclose()
...
}
Для динамических объектов можно использовать смарт указатели. Например: shared_ptr (boost::shared_ptr, std::shared_ptr) или std::auto_ptr
Предпочтительнее использовать shared_ptr<>, т.к. внутри него есть счётчик ссылок, что позволяет его безопасно копировать и хранить в контейнерах.
Для строк std::string или тот класс строк, который использует ваш фреймворк. CString, например.
Для массивов, с некоторыми оговорками, можно использовать std::vector<>. Даже в том случае, когда нужно получить прямой указатель на массив объектов.
Для остальных ресурсов, часто фреймворк, который вы используете, уже предоставляет классы-обёртки. Поищите их и пользуйтесь. Например, CFile,CPen,CBrush, CDC и т.д.
Для ваших собственных ресурсов вполне можно написать такую же поделку, как я привёл выше.
"Пишите так, будто ошибок в программе нет"
Подход призывает правильно пользоваться исключениями и выгодами, которые он предоставляет.
В противовес подхода, когда код ошибки возвращается из функции и обрабатывается с помощью толпы if().
Тема сложная, не берусь комментировать. Лучше почитать в книжке.
"Язык С++" "Бьёрн Страуструп" — библия программиста С++
Невероятно удачное описание, удачные примеры как самого языка, библиотеки STL, так и общего подхода к разработке ПО.
"Андрей Александреску: Современное проектирование на С++" — книга скорее вредная, чем полезная.
Она производит сильное впечатление, у читателя сразу случается приступ шаблонизма.
Хотя сам Александреску призывает разумно использовать шаблоны и не заменять ими наследование.
В результате в проекте сразу появляется куча ненужных шаблонов и шаблонных параметров.
Не обошла и меня в свое время эта тенденция.
Такие шаблоны в проекте не нужны:
template<class Loger>
class GsmModem;
template<class Loger>
class GsmModem : public GenericModem<Loger>,public igsm_modem
{
...
};
#define TEMPLATE_INFDEV \
template\
<\
int infdev_id,\
template <class> class CarrierPolicy,\
class CarType,\
template <class> class CarContainerPolicy,\
class DevLogType,\
template <class> class ThreadingPolicy,\
template <class> class PollQueuePolicy,\
template <class> class PollErrorPolicy,\
template <class> class DirReqQueuePolicy,\
template <class> class CustReqQueuePolicy\
>
#define INFDEV infdev<infdev_id,CarrierPolicy,CarType,CarContainerPolicy,DevLogType,ThreadingPolicy,PollQueuePolicy,PollErrorPolicy,DirReqQueuePolicy,CustReqQueuePolicy>
template
<
int infdev_id,
template <class> class CarrierPolicy,
class CarType,
template <class> class CarContainerPolicy=CarContainer,
class DevLogType=DevLog,
template <class> class ThreadingPolicy=SingleThread,
template <class> class PollQueuePolicy=PollQueue,
template <class> class PollErrorPolicy=PollError,
template <class> class DirReqQueuePolicy=DirReqQueue,
template <class> class CustReqQueuePolicy=CustReqQueue
>
class infdev;
Вывод. Книжку читать, она сильно меняет взгляд на понимание С++. Но не надо сразу кидаться воплощать идеи в книжке в своём проекте.
Здравствуйте, Шебеко Евгений, Вы писали:
ШЕ>Хочу представить такие рекомендации новичкам в C++.
рекомендую это сделать в блоге: http://rsdn.ru/forum/news/4887194.flat
Здравствуйте, Шебеко Евгений, Вы писали:
ШЕ>Нет никаких причин их использовать.
Вообще-то нет никакого разумного способа обойтись без их использования. Ибо любой фреймворочный класс строк инициализируется из C-строки. Ну это так, 5 коп.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[2]: Не надо почленно копировать объект, используйте static_cast.
On 09/18/2012 02:46 PM, Шебеко Евгений wrote:
> Часто нужно присвоить потомку содержимое базового класса другого объекта. > Неявный operator=() потомка не работает, когда присваивается базовый класс. > Не надо почленно копировать объект, используйте static_cast.
Евгений, текст правила нужно переформулировать так:
"Никогда не нужно присваивать потомку содержимое базового класса другого
объекта. Если вы вынуждены так делать, то вам нужно пересмотреть дизайн
вашей программы."
Мне ни разу в жизни наверное не пришлость делать такой ужас.
Если же это надо делать, то нужно реализовать в наследнике
конструктор от класса-предка, и там правильно сконструировать
объект и инициализировать недостающие поля.
Здравствуйте, Шебеко Евгений, Вы писали:
ШЕ>Почему-то довольно часто приходится слышать от C++ программистов, что они не используют стандартную библиотеку.
Очень многие при использовании фреймворка стараются использовать только те элементы стандартной библиотеки, которых нет в используемом фреймворке. В результате и получаем "не используют стандартную библиотеку".
ШЕ>Зато каждый четвёртый кандидат на должность C++ программиста, никогда не слышал о STL.
Это прискорбно. С другой стороны, если заниматься буквоедством, и, ЕМНИП, такого слова STL больше нет. Есть просто стандартная библиотека C++, включившая в себя то, что раньше называлось STL (если я не прав -- поправьте).
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, Шебеко Евгений, Вы писали:
ШЕ>По эффективности все они работают также, а иногда даже лучше, чем ваш собственный код на C строках.
эффективности чего?
Вы стали бы писать парсер/генератор который использовал бы std::string ?
> Почему-то довольно часто приходится слышать от C++ программистов, что они не > используют стандартную библиотеку. > Согласитесь странно было бы слышать от программиста на JavaScript или PHP: "Я не > использую строки" или "Я не использую массивы или ассоциативные массивы".
О!, Тут можно дооолго и нууудно распинаться. К сож. С++ тут очень сильно
отличается от других языков. Начнём с того, что стандартная библиотека языка С++
появилась много позже языка С++. Также как ни странно стандартная библиотека
языка С++, её дизайн, идёт вразрез со всеми концепциями и парадигмами языка С++,
существовавшими до неё и помимо неё. Т.е. грубо говоря, стандартная библиотека
языка С++ далеко не универсальна, поэтому применимость её достаточно ограничена.
Так что не удивляйтесь, если вам кто-то скажет, что он не применяет STL.
> Зато каждый четвёртый кандидат на должность C++ программиста, никогда не слышал > о STL. > Каждый второй кандидат на предложение отсортировать массив, предлагает > сортировку пузырьком!
Ну, иногда это приемлимо, а иногда это даже лучший способ.
> Только два кандидата, с помощью гугла, смогли дать более-менее рабочий код с > использованием std::sort().
> Между тем основное, что есть в библиотеке это контейнеры и алгоритмы.
алгоритмы вообще может быть не нужны в данном приложении. Там порядка 50
процентов алгоритмов математические и статистические алгоритмы, они далеко не
во всех приложениях используются.