Pull to refresh

Comments 91

Обидно, что такие грубые ошибки встречаются в достаточно серьезных проектах. Хотя это материал 3го курса института.
Хотел бы я учиться в том институте, где такое расскзывают хотябы на пятом.
Знание таких моментов приходит с опытом, этому в институтах не учат, насколько я заметил. Человеку недостаточно просто прочитать полностью стандарт языка, чтобы сразу начать писать на нём правильно.
От одного прочтения правил русского языка тоже чильно грамотней писать не станешь. Но тем не менее, учат же )
UFO just landed and posted this here
Ни в коем случае не хочу обидеть, но читать вот такие посты про PVS-Studio ГОРАЗДО интереснее!
Согласен. Только единорога всё-таки стоит, имхо, оставить символом постов Andrey2008
Новая версия дробовика все также прострелит вам ногу, если вы нажмете курок.
Ну так дробовики кому попало и не выдают обычно. Нужна как минимум справка от психиатра.
Прострелит. Но в причинах введения delegated constructors как раз указан пример из статьи автора.
Курок не нажимают, курок спускают.
Нажимают на спусковой крючок или шнеллер.

Вот так, на этом простом примере мы видим, что надо понимать что ты делаешь.
В делегировании конструкторов момент с двойной инициализацией учтен.
UFO just landed and posted this here
ну, может надо поставить дефис после слова программисты?
UFO just landed and posted this here
Никогда до конца не понимал этой фразы. Объясните, как вы ее понимаете?

Мне вот кажется что ленивые программисты === не очень хорошие специалисты своей профессии.
Например ленивый математик не будет проверять все условия теоремы, ибо лень, либо воспользуется каким-либо неоптимальным методом решения задачи. Аналогия может и не полная, но всё же присутствует.
Источником фразы обычно служат высказывания типа «набирать 100 раз подряд код типа „… if (i == 42) print «42» else if (i == 43)...¨ может только очень неленивый человек».
Ну и так же утверждение, что ленивый программист (кстати, про математиков так тоже говорят) будет искать максимально короткое и оптимальное решение, чтобы облегчить себе труд. Потому что писать объёмное и запутанное ему лень.
Ну вот пример: проверять входящие данные в web-приложении может быть лениво, т.е. максимально короткое решение — не проверять их, чтобы облегчить себе труд, потому-что писать объемный и запутанный abstraction-layer для проверки данных ему лень. Вы про это?
Это не «лениво», это «не выполнил поставленную задачу».
Неленивый программист будет честно проверять каждый раз все входные данные. Ленивый напишет один раз список типов данных с функциями-валидаторами для каждого и при поступлении данных они будут автоматически валидироваться. Для добавления каких-то новых входных данных достаточно будет указать их тип.
Спасибо, я вас понял) Действительно, почему-то воспринимал фразу про лень только в негативном контексте. Вот только таких «положительно-ленивых» я встречаю редко. Всё больше из моего примера…
а потом латать дыры ему по всему коду не лень значит? Вы привели как раз пример не ленивого программиста, ленивый сделает один раз хорошо, и забудет про этот код.
Эээ… А кто вообще так делает и зачем?

Уже просто использование конструкции new(var) Type(args) предполагает такой уровень владение языком, до которого большинство студентов просто не доходит.
А опытному программисту, как правило, написать лишнюю функцию труда не составит
Получается как раз всё наоборот. Студент не знает про такое и напишет нормальный Init(). А продвинутый программист — Смотрите Как Я Могу! И ошибается. :)
С каких это пор опытные программисты принимают решения с формулировкой «Смотрите Как Я Могу»?
Формулировка может быть другой. «А здесь изящно будет смотреться вот такой шаблонный класс из Александреску». А потом другой программист не может понять, что там написано. И не потому, что он плох. А потому, что первый как раз не удержался от «вот как я могу». :)
Так что бывает, бывает…
ИМХО, пока в С++ не введут нормальные концепты «шаблонный класс из Александреску» вообще нигде и никогда не будет смотреться изящно. Потому что есть 150 мест, где можно налажать.
Не надо строить иллюзий — 150 легких способов налажать есть в любой части плюсов.
Где-то в недрах:

struct Point { int x, y; };

Понадобилась реализация помощнее…
~~~~~~~~~~~

Студент:

class MyPoint
{
public:
  MyPoint(Point p) { MyPoint(p.x, p.y); }
  MyPoint(int x, int y) { this->x = x; this->y = y; }
private:
  int x, y;
};


<мысли>Хм, что-то не так работает. Как же вызвать конструктор?
Ай, лучше вынесу общую часть отдельно.</мысли>


class MyPoint
{
private:
  void Init(int x, int y) { this->x = x; this->y = y; }
public:
  MyPoint(Point p) { Init(p.x, p.y); }
  MyPoint(int x, int y) { Init(x, y); }
private:
  int x, y;
};

<мысли>Какой я молодец! Пойду поем.</мысли>
~~~~~~~~~~~

Продвинутый программист:

template <typename T>
class PointEx
{
public:
  PointEx(T x, T y) : x(x), y(y) {};
  PointEx(const Point&);
private:
  T x, y;
};

template<>
PointEx<int>::PointEx(const Point& point) { this->PointEx::PointEx(point.x, point.y); }



<мысли>Шикарно, шикарно!!! Теперь можно и хабр почитать.</мысли>
а можно немного понудить?
<нужу>
а что если шаблонным параметром будет не int и не float и даже не double, а какой-нибудь class XAxisCoord? я бы не объявлял PointEx(const Point&);, а сделал бы что-то вроде PointEx makeFromPoint(const Point &); не членом класса.
</нужу>
Сорри, просто давно ничего не проектировал, не писал - соскучился по этому делу.
кхм, не сразу вдуплил в теги, вот прототип функции:
PointEx<int> makeFromPoint(const Point &);
Я об этом подумал. Код написан просто как демонстрация того, что знание разных фишечек C++ без правильного их применения ничего не значит. В нём простенький вариант «студента» содержит меньше ошибок проектирования, чем вариант «продвинутого программиста».
На самом деле такая конструкция указана в половине учебников, так что студенту никто не мешает взять дробовик в свои хрупкие руки)
Заверяю вас, этот раздел учебника студентами даже не читается!
UFO just landed and posted this here
Поэтому новичкам лучше использовать clang, там очень хорошие и подробные диагностики:
clang++ -cc1 -analyze -analyzer-checker=core test-sizeof.cpp
test-sizeof.cpp:5:29: warning: sizeof on array function parameter will return size of 'int *' instead of 'int []'
  for (int i = 0; i < sizeof(arr); i++)
                            ^
test-sizeof.cpp:3:12: note: declared here
void p(int arr[])
           ^
1 warning generated.


Если есть ещё примеры — давайте.
А ещё новичкам лучше использовать стандартные контейнеры вместо сишных массивов.
Я вот думаю, что новичкам нужно либо давать язык высокого уровня (Java/Python, да хоть PHP в конце-то концов), либо C. Таким образом, изучается какой-то один аспект программирования — либо алгоритмы, либо низкоуровневая работа с памятью. А вот C++ нужно брать уже потом — когда известно ООП и алгоритмы, либо понятны операции с указателями и нотация С. Тогда можно браться за С++.
P.S. Я вот себе на днях купил Кернигана/Ричи и Страуструпа. Начал с первой ;)
Очень разумно. Но вы понимаете, «Си старый, хочется что-то поновее».
О да. Сам по этой причине раньше не изучал его.
Это вопрос спорный. Многие люди, изучив С перед С++, пишут потом на С++ в стиле С, что не очень хорошо. Работу с памятью вполне можно и в контексте С++ изучить.
Ну, есть такой термин «Си с классами», на котором писались Imaging, Doom 3, только из того, что мне известно. И ведь не такие уж плохие программы, правда? Так что мне кажется, такой подход имеет право на жизнь.
Я у Си с классами преимуществ перед полноценным С++ не вижу никаких. То, что на нём написаны Doom3 и Imaging ещё ничего не значит. Может там в архитекторах динозавры, которые 20 лет до этого писали на Си, а опыта С++ у них мало. Естественно качество кода на «Си с классами» у них будет выше, чем на С++, и лучше использовать именно его. Но в 2012 году писать на Си с классами как-то странно по-моему.
Там есть одно существенное преимущество — максимально полный контроль. Массово это не нужно, согласен.
А по поводу Doom3 вы правы — Кармак известный фанат Си.
Есть такое преимущество. Называется RAII.
RAII — это преимущество Си с классами перед С++? То есть RAII есть в Си с классами, но его нет в С++, я правильно вас понял?
Нет, это я неверно сформулировал мысль. Естественно, RAII есть в С++, но как только вы начинаете полноценно использовать RAII, от «Си с классами» остается уже очень мало.
Вот потому я и не пишу [] в списке параметров!
Всегда считал, что опытный програмист будет использовать список инициализации в двух конструкторах, вместо того, чтобы добавлять функцию инциализации.
Придется продублировать список в каждом конструкторе, это затрудняет поддержку.
Для этого существует code convention. Если человек привык, что после добавления поля в класс, он добавляет это поле во всех конструкторы в список инициализации, то проблем не возникнет.
Никакие соглашения и привычки не отменят человеческий фактор. Всегда есть риск банально забыть или ошибиться.
Конечно, всегда есть риск ошибиться. Но если не использовать потенциально опасные стороны языка C++ (список инициализации, наверное, наймение опасная сторона С++), то может лучше использовать C# или Java?
На самом деле в стандарт C++11 включена возможность делегирования конструкторов и на мой взгляд это одна из самых интересных (наравне с лямбда-функциями, range-based for и auto) особенностей нового стандарта :)

Увы, на данный момент стабильные версии компиляторов эту возможность не поддерживают (например, делегирование конструкторов появится в GCC 4.7).
Плюсы — это лоцманские воды. Чуть что не так — и уже сидишь на рифах.
Ну а что делать, если программист не прочитал как определяется конструктор в C++, то тут нечего на язык пенять.
Плюсисту-джуниору надо как минимум прочитать, понять и проработать всего Страустрапа, сеньору — Александреску. У плюсов извилистая история и множество неочевидных правил. И на язык тому, кто взялся на нем программировать в самом деле нечего пенять — надо знать свой рабочий инструмент.
Александреску я не осилил, так и лежит на столе :) Но мне кажется, что достаточно заглядывать C++ FAQ lite и таких ляпов точно не будет.
сеньору минимум читать Александреску? Я считаю последнее — это программирование ради программирования, кому нечем больше заняться, сродни задачкам на сообразительность. Я бы за километры подобного шаблоидно-монстроидного кода руки бы поотрывал. Особенно весело когда код нужно компилить не только под студией, а под какой-нить «бородатой» версией gcc (
Стоит ли использовать placement new для сериализации POD структуры для, например, отправки её по сети?
Не понял вопрос. Может и можно. А зачем? Не вижу связи между placement new и задачей сериализации.
Я извиняюсь за долгий ответ, правила хабрахабра. Теги также не проходят.

Скажем, стоит задача отправить по сети пакет, сформированный из структуры с данными. Допустим, необходимо обеспечить время жизни этого пакета до окончания его отправки. Необходимо уметь писать и читать структуру из этого пакета.

1. Реализовать для структуры методы serialize() и deserialize(). Проблема кроется в том, что при изменении в заголовочном файле состава структуры можно забыть обновить эти методы. Да и добавление нового функционала не должно требовать изменения старого кода.

2. Просто копировать структуру (конечно, обеспечить простую структуру с pod членами, размер которых не зависит от платформы, выравнивание которой установлено в 1)
void OutStruct::write(std::vector& buffer)
{
data.resize(sizeof(OurStruct));
new (&data.front()) OurStruct(*this);
}
Код написан прямо в форме и может содержать ошибки. Он некрасив, но так я его себе представляю, он избавит нас от необходимости его поддержки при изменении структуры OurStruct.

Является ли приведенный мною пример исключением, которое имеет право на жизнь?
Если данные состоят из POD членов и передаются в сыром виде в упакованной структуре, так может здесь не надо делать класс?
Сделать простую Си структуру. И работать с ней как с буфером памяти, копировать используя memcpy и т.п. А в класс включить эту струтктуру как член и вычитывать из её полей данные. И не нужно никаких хаков для создания классов.
Конечно, я скорее всего не представляю суть задачи. Вам виднее и возможно такой способ удобен и наиболее логичен. Вы многого от меня хотите. Я с наскока все равно толковое не посоветую. :)

Мне действительно следовало спрашивать на stackexchange. Спасибо за попытку! :)
Порядок байтов, представление чисел с плавающей — эти параметры могут отличаться.
А ещё упаковка с выравниванием, на которые тактично комментом ниже намекает shodan. Ух, сколько эффектов могут ждать! От кривых данных до сегфолтов.
А потом придет злой дядя Вася и разбавит структуру указателями (кстати, указатели — это тоже POD).

Я не вижу никаких преимуществ у placement new перед обычным memcpy
Результаты исполнения на разных платформах (и даже тупо сборки разными компиляторами) могут сильно удивить.
UFO just landed and posted this here
По отношению к программистам ленивость заключается не в сидении на диване с газетой по 28 часов в сутки, а в их стремлении максимально сократить затраты на написание кода. Есть даже такая народная мудрость: «Самую сложную задачу нужно поручать самому ленивому программисту, потому что он найдёт для неё самое короткое решение».
— а можно узнать — какие достоинства у программистов?
— многие из вас знакомы с достоинствами программиста. Их всего три, и разумеется это: лень, нетерпеливость и гордыня. © Larry Wall
— a какие недостатки?
— программисты идеальны. у них нет недостатков
class SomeClass
{
int x,y;
public:
SomeClass(int xx=0, int yy=0) : x(xx), y(yy) {}
};


так то почему не написать?
А как это относится к теме статьи? И так понятно, что приведенный демонстрационный код можно написать красивей.
К тому что пример не удачный совершенно. Перед { в конструкторе уже весь класс инициализирован. Если там были const int, то была бы ошибка ещё на этапе компиляции.
Статья немного устарела, в С++11 можно один конструктор из другого вызывать. Ну а в старом добром 2003-м нужно просто запомнить, что нельзя да и не заниматься «вот как я могу».
Ничего подобного. Актуальнее не бывает. Вы думаете, как только в языке появляется новая возможность, программисты начинают перелопачивать миллионы строк кода в поисках места, где старый фрагмент можно написать теперь лучше? Или все сразу начинают пользоваться новыми конструкциями, которые поддерживаются только несколькими компиляторами, жертвуя переносимостью?
Я не сказал, что не актуальна, несомненно С++11 еще не скоро будет массовым в девелопменте.
Но все же вы просто проигнорировали изменение в стандарте, никак не упомянув об этом, отчего я и написал «немного устарела».
а что за старый добрый 2003 не объясните? предыдущий стандарт был 98 года.
Вот ведь я облажался, я действительно забыл об этом :(
habrahabr опустился до уровня детского сада. Я понимаю, что сейчас детских садов не хватает, но чтобы статья для идиотов нахватала столько +?
Ну любят тут умные люди, не знающие елементарных вещщей порассусоливать на якобы подлые злободневные темы. Меня убило вот это.

CSlideBarGroup::CSlideBarGroup(CSlideBarGroup& Group)
{
CSlideBarGroup(
Group.GetName(), Group.GetIconIndex(), Group.GetListBox());
}
И подпись: Ничего подобного. Здесь создается и тут же уничтожается новый неименованный объект типа CSlideBarGroup.
К.О. Рыдает горячими слезами.
Это для джуниуров у уровнем АйКью 50 балов.

Если бы только вы могли знать, как вы заблуждаетесь. Такие ошибки допускают люди с многими годами опыта. В Visual C++ даже есть предупреждение C9430 на один из случаев такой ситуации.
Но как человек с многими годами опыта не может понять что то что выше равносильно тому что ниже.

CSlideBarGroup::CSlideBarGroup(CSlideBarGroup& Group)
{
CSlideBarGroup object=CSlideBarGroup(Group.GetName(), Group.GetIconIndex(), Group.GetListBox());
}
//или
CSlideBarGroup::CSlideBarGroup(CSlideBarGroup& Group)
{
CSlideBarGroup object(Group.GetName(), Group.GetIconIndex(), Group.GetListBox());
}

А что произойдет с локальным екземпляром класса object по завершению функции — конструктора?
Людям свойственно не быть все время предельно внимательными. Совершенно типичная ситуация, когда один разработчик с годами опыта пишет код, проверяет его, потом другой его открывает — и сразу находит пару нелепейших ошибок.
Я таких на работу не работу не беру.

А люди с многими годами опыта даже языка не знаю на котором пишут. Это одно из необходимых условий.
А как насчет такой реализации оператора присвоения?

Foo& Foo::operator=(const Foo& src)
{
if (this != &src)
{
this->Foo::~Foo();
this->Foo::Foo(src);
}
return *this;
}

Я вижу здесь преимущество в том, что не надо вручную вызывать оператор присвоения базового класса и достаточно одной реализации копирования объектов в конструкторе копирования.

Хотелось бы узнать насколько опасна такая конструкция?
Рассмотрим код вида:
void Foo()
{
  Class A, B;
  A = B;
}

Представим, что в момент «this->Foo::Foo(src); » возникает исключение. Тогда получается, что когда летит исключение, объект A уже разрушен. И при выходе из функции Foo() объект A будет уничтожаться ещё один раз.
Sign up to leave a comment.