TOP граблей С++ для новичков
От: Шебеко Евгений  
Дата: 18.09.12 10:41
Оценка: 10 (3) -1
Сейчас приходится собеседовать много новичков.
Заметил что ничего в жизни не меняется.
Даже люди с опытом работы в крупных наступают на те же грабли, на которые наступал я в своё время.

Хочу представить такие рекомендации новичкам в 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= по умолчанию.
От: Шебеко Евгений  
Дата: 18.09.12 10:43
Оценка:
Если класс не содержит явного копирующего конструктора или operator=, то они создаются неявно.
Об этом надо помнить.

Допустим у нас есть класс, который содержит в себе указатель на свой буффер.



class buf_t
{
  unsigned char* data;
public:
  buf_t(size_t buf_size)
  {
    data=new unsigned char[buf_size];
    printf("buf_t(): data=%p\n",data);
  }

  ~buf_t()
  {
    printf("~buf_t(): data=%p\n",data);
    delete[] data;
  }

};


Легко ошибиться и выполнить присваивание или вызвать копирующий конструктор для такого объекта.
Вот такой код запросто скомпилируется и будет работать неправильно.


int main(int argc,char** argv)
{
  buf_t a(1000);

  buf_t b(a);

  buf_t c(500);
  c=a;


  return 0;
}


Вывод программы будет такой:


buf_t(): data=00902DCC
buf_t(): data=009031B8
~buf_t(): data=00902DCC
~buf_t(): data=00902DCC
~buf_t(): data=00902DCC



Т.е. мы присвоили указатель data объекта a объектам b и c.
В результате у нас 3 раза освободился один и тот же указатель, а data объекта c вообще потерялся.

Решение — сделать private копирующий конструктор и operator=. При этом достаточно их просто объявить, тело функций добавлять не надо.


class buf_t
{
  unsigned char* data;
public:
  buf_t(size_t buf_size)
  {
    data=new unsigned char[buf_size];
    printf("buf_t(): data=%p\n",data);
  }

  ~buf_t()
  {
    printf("~buf_t(): data=%p\n",data);
    delete[] data;
  }

private:
    buf_t(const buf_t&);
    void operator=(const buf_t&);

};
Re: delete и delete[] различаются
От: Шебеко Евгений  
Дата: 18.09.12 10:44
Оценка:
Если вы создаёте объект, то для его разрушения должны вызывать delete.
Если вы создаёте массив, то для его разрушения должны вызывать delete[].
Иначе ваш код будет работать неправильно.
Для компилятора указатель на объект и указатель на массив неразличимы, поэтому это ваша проблема следить, какой delete вызвать.


item_t* v= new item_t;
...
delete v;

item_t* v= new item_t[100];
...
delete[] v;


Что будет, если для массивов вызвать обычный delete?
Либо осовободится только один объект, либо программа упадёт.
Можете сами в этом убедится, выполнив такой код:

#include <stdio.h>
#include <string>

struct item_t
{
  std::string name;

  item_t()
  {
    printf("item_t()\n");
  }

  ~item_t()
  {
    printf("~item_t()\n");
  }
};


int main(int argc,char** argv)
{
  item_t* v=new item_t[100];
  delete v;

  return 0;
}
Re: Не надо почленно копировать объект, используйте static_cast.
От: Шебеко Евгений  
Дата: 18.09.12 10:46
Оценка: 4 (2) -1
Часто нужно присвоить потомку содержимое базового класса другого объекта.
Неявный 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;
}
Re: Виртуальный деструктор
От: Шебеко Евгений  
Дата: 18.09.12 10:48
Оценка: 1 (1)
Если вы планируете удалять производный класс, используя указатель на базовый класс, вы должны сделать дестурктор базового класса виртуальным.
Иначе деструктор потомка не будет вызван.
Это также касается случая, когда в вашем производном классе нет явного деструктора. В нём могут быть переменные (например, типа std::string) для которых нужно вызвать деструктор.


#include <stdio.h>
#include <string>

struct human_t
{
};

struct women_t : public human_t
{
    std::string maiden_name;

    ~women_t()
    {
        printf("~women_t()\n");
    }

};


int main(int argc,char** argv)
{
    human_t* v=new women_t;
    printf("before delete\n");
    delete v;
    printf("after delete\n");
    return 0;
}


Вывод:

before delete
after delete



Если изменить на:

#include <stdio.h>
#include <string>

struct human_t
{
    virtual ~human_t(){}
};

struct women_t : public human_t
{
    std::string maiden_name;

    ~women_t()
    {
        printf("~women_t()\n");
    }

};


int main(int argc,char** argv)
{
    human_t* v=new women_t;
    printf("before delete\n");
    delete v;
    printf("after delete\n");
    return 0;
}


То вывод будет таким, как ожидалось:

before delete
~women_t()
after delete
Re: forward declaration и оператор delete
От: Шебеко Евгений  
Дата: 18.09.12 10:50
Оценка: 8 (4) +1
delete не может вызвать деструктор для тех объектов, чьё объявление классов он не видит.
В таком случае просто освободится память, занимаемая объектом.

unit1.cpp:

#include <stdio.h>

struct human_t;
human_t* create_human();


int main(int argc,char** argv)
{
    human_t* v=create_human();
    printf("before delete\n");
    delete v;
    printf("after delete\n");
    return 0;
}



unit2.cpp:
#include <stdio.h>

struct human_t
{
    virtual ~human_t()
    {
        printf("~human_t()\n");
    }
};

human_t* create_human()
{
    return new human_t;
}




Вывод:

before delete
after delete



Т.е. деструктор ~human_t() не вызвается.
Re: Не используйте голые строки C
От: Шебеко Евгений  
Дата: 18.09.12 10:51
Оценка: -8
Нет никаких причин их использовать.
Класс строк есть в 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()))
Re: Поймите и используйте STL
От: Шебеко Евгений  
Дата: 18.09.12 10:52
Оценка: +1 -1
Почему-то довольно часто приходится слышать от 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 по фамилии и имени.


Решение довольно простое:

#include <string>
#include <vector>
#include <algorithm>

struct human_t
{
    std::string first_name;
    std::string last_name;
    unsigned age;
    
    human_t()
    {
        age=0;
    }
};

//предикат
struct human_pr
{
    inline bool operator()(const human_t& a,const human_t& b) const
    {
        if(a.last_name!=b.last_name)return a.last_name<b.last_name;
        return a.first_name<b.first_name;
    }
};


int main(int argc,char** argv)
{
    std::vector<human_t> v;

    std::sort(v.begin(),v.end(),human_pr());

    return 0;
}
Re: Resource Acquisition Is Initialization
От: Шебеко Евгений  
Дата: 18.09.12 10:54
Оценка:
здесь

Старайтесь избегать в коде явного освобождения ресурсов.
Причины для этого такие:
— Вы можете выйти из области видимости (например, 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 и т.д.
Для ваших собственных ресурсов вполне можно написать такую же поделку, как я привёл выше.
Re: Научитесь пользоваться исключениями
От: Шебеко Евгений  
Дата: 18.09.12 10:55
Оценка: -3
"Пишите так, будто ошибок в программе нет"
Подход призывает правильно пользоваться исключениями и выгодами, которые он предоставляет.
В противовес подхода, когда код ошибки возвращается из функции и обрабатывается с помощью толпы if().

Тема сложная, не берусь комментировать. Лучше почитать в книжке.
Re: Книги
От: Шебеко Евгений  
Дата: 18.09.12 10:56
Оценка: 1 (1) -1
"Язык С++" "Бьёрн Страуструп" — библия программиста С++
Невероятно удачное описание, удачные примеры как самого языка, библиотеки 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;

Вывод. Книжку читать, она сильно меняет взгляд на понимание С++. Но не надо сразу кидаться воплощать идеи в книжке в своём проекте.
Re: TOP граблей С++ для новичков
От: uzhas Ниоткуда  
Дата: 18.09.12 11:01
Оценка: +4
Здравствуйте, Шебеко Евгений, Вы писали:

ШЕ>Хочу представить такие рекомендации новичкам в C++.

рекомендую это сделать в блоге: http://rsdn.ru/forum/news/4887194.flat
Автор: IT
Дата: 10.09.12
Re: Стандарты программирования на С++
От: igna Россия  
Дата: 18.09.12 11:05
Оценка: +1
Здравствуйте, Шебеко Евгений, Вы писали:

ШЕ>Хочу представить такие рекомендации новичкам в C++.


А чем твои советы лучше чем Стандарты программирования на С++, <i>Герб Саттер, Андрей Александреску</i>?
Re[2]: Стандарты программирования на С++
От: slava_phirsov Россия  
Дата: 18.09.12 11:09
Оценка: +1
Здравствуйте, igna, Вы писали:

I>А чем твои советы лучше чем Стандарты программирования на С++, <i>Герб Саттер, Андрей Александреску</i>?


Практически все советы ТС можно найти у моего любимого Майерса ("Effective C++", "More effective C++").
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[2]: Не используйте голые строки C
От: slava_phirsov Россия  
Дата: 18.09.12 11:11
Оценка:
Здравствуйте, Шебеко Евгений, Вы писали:

ШЕ>Нет никаких причин их использовать.


Вообще-то нет никакого разумного способа обойтись без их использования. Ибо любой фреймворочный класс строк инициализируется из C-строки. Ну это так, 5 коп.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[2]: Не надо почленно копировать объект, используйте static_cast.
От: MasterZiv СССР  
Дата: 18.09.12 11:16
Оценка: 5 (2) +4
On 09/18/2012 02:46 PM, Шебеко Евгений wrote:

> Часто нужно присвоить потомку содержимое базового класса другого объекта.

> Неявный operator=() потомка не работает, когда присваивается базовый класс.
> Не надо почленно копировать объект, используйте static_cast.

Евгений, текст правила нужно переформулировать так:

"Никогда не нужно присваивать потомку содержимое базового класса другого
объекта. Если вы вынуждены так делать, то вам нужно пересмотреть дизайн
вашей программы."

Мне ни разу в жизни наверное не пришлость делать такой ужас.
Если же это надо делать, то нужно реализовать в наследнике
конструктор от класса-предка, и там правильно сконструировать
объект и инициализировать недостающие поля.
Posted via RSDN NNTP Server 2.1 beta
Re[2]: Поймите и используйте STL
От: slava_phirsov Россия  
Дата: 18.09.12 11:16
Оценка: +1
Здравствуйте, Шебеко Евгений, Вы писали:

ШЕ>Почему-то довольно часто приходится слышать от C++ программистов, что они не используют стандартную библиотеку.


Очень многие при использовании фреймворка стараются использовать только те элементы стандартной библиотеки, которых нет в используемом фреймворке. В результате и получаем "не используют стандартную библиотеку".


ШЕ>Зато каждый четвёртый кандидат на должность C++ программиста, никогда не слышал о STL.


Это прискорбно. С другой стороны, если заниматься буквоедством, и, ЕМНИП, такого слова STL больше нет. Есть просто стандартная библиотека C++, включившая в себя то, что раньше называлось STL (если я не прав -- поправьте).
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[2]: Не используйте голые строки C
От: Abyx Россия  
Дата: 18.09.12 11:20
Оценка: +2
Здравствуйте, Шебеко Евгений, Вы писали:

ШЕ>По эффективности все они работают также, а иногда даже лучше, чем ваш собственный код на C строках.

эффективности чего?
Вы стали бы писать парсер/генератор который использовал бы std::string ?
In Zen We Trust
Re[3]: Стандарты программирования на С++
От: igna Россия  
Дата: 18.09.12 11:20
Оценка: +1
Здравствуйте, slava_phirsov, Вы писали:

_>Практически все советы ТС можно найти у моего любимого Майерса ("Effective C++", "More effective C++").


Вопрос-то не в том, а в том, зачем нужны советы от Шебеко.
Re[2]: Поймите и используйте STL
От: MasterZiv СССР  
Дата: 18.09.12 11:24
Оценка: +1 -3
> Почему-то довольно часто приходится слышать от C++ программистов, что они не
> используют стандартную библиотеку.
> Согласитесь странно было бы слышать от программиста на JavaScript или PHP: "Я не
> использую строки" или "Я не использую массивы или ассоциативные массивы".

О!, Тут можно дооолго и нууудно распинаться. К сож. С++ тут очень сильно
отличается от других языков. Начнём с того, что стандартная библиотека языка С++
появилась много позже языка С++. Также как ни странно стандартная библиотека
языка С++, её дизайн, идёт вразрез со всеми концепциями и парадигмами языка С++,
существовавшими до неё и помимо неё. Т.е. грубо говоря, стандартная библиотека
языка С++ далеко не универсальна, поэтому применимость её достаточно ограничена.
Так что не удивляйтесь, если вам кто-то скажет, что он не применяет STL.

> Зато каждый четвёртый кандидат на должность C++ программиста, никогда не слышал

> о STL.
> Каждый второй кандидат на предложение отсортировать массив, предлагает
> сортировку пузырьком!

Ну, иногда это приемлимо, а иногда это даже лучший способ.

> Только два кандидата, с помощью гугла, смогли дать более-менее рабочий код с

> использованием std::sort().

> Между тем основное, что есть в библиотеке это контейнеры и алгоритмы.


алгоритмы вообще может быть не нужны в данном приложении. Там порядка 50
процентов алгоритмов математические и статистические алгоритмы, они далеко не
во всех приложениях используются.
Posted via RSDN NNTP Server 2.1 beta
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.