Как делать не надо

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Сионист писал(а):

Код: Выделить всё

class A
{
 int *Data;
 public:
  A ()
  {
   Data=new int [1000000];
  };
 ~A ()
 {
  delete [] Data;
 }
 A (const A &a)
 {
  int *p1;
  int *p2;
  Data=new int [1000000];
  for (p2=Data+999999, p1=a.Data+999999; p2>=Data; --p1, --p2)
  {
   *p2=*p1;
  }
 }
 A operator = (const A &a);
 {
  int *p1;
  int *p2;
  for (p2=Data+999999, p1=a.Data+999999; p2>=Data; --p1, --p2)
  {
   *p2=*p1;
  }
  return *this;
 }
 ...
};
...
A a;
a=a;
Кстати, это тоже хороший пример того, как делать не надо. Этот код не прошёл бы у меня ревью и был бы завёрнут на доработку. Копи-паст - это зло. Как только внутренняя структура класса меняется, то нужно будет не забыть поменять два места. В подобных случаях обычно вводится утилитарный метод, позволяющий избавиться от дубликации кода. После добавления проверки на копирование в самого себя, код будет выглядеть следующим образом:

Код: Выделить всё

class A
{
 int *Data;

 public:
  A ()
  {
   Data=new int [1000000];
  };

 ~A ()
 {
  delete [] Data;
 }

 A (const A &a)
 {
  Data=new int [1000000];
  CopyFrom(a);
 }

 A operator = (const A &a);
 {
  if (&a != this)
  {
   CopyFrom(a);
  }
  return *this;
 }

 ...

private:
 void CopyFrom(const A& a)
 {
  int *p1;
  int *p2;
  for (p2=Data+999999, p1=a.Data+999999; p2>=Data; --p1, --p2)
  {
   *p2=*p1;
  }
 }
};
...
A a;
a=a;
И ещё одно замечание. Хардкод значений (в нашем случае 1000000) так же не допустим. От этого нужно отучать программистов на самом начальном этапе их карьеры. Это значение должно быть вынесено в константное статическое поле класса.

И, пожалуй, последнее замечание. Возвращение this по значению из оператора присваивания при том, что каждый объект этого класса весит минимум 4 мегабайта - это просто убийство. Конечно же не хватает амперсанда. Ребята, не делайте так никогда! Подобный код (взависимости от того, насколько он часто вызывается), может замедлить вашу программу в тысячи раз.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Romeo писал(а):Ты подобной фразой пытаешься поставить под сомнение тот факт, что три точки выполняются каждый раз при вызове функции?

Вот программа:

Код: Выделить всё

#include <iostream>

int& f()
{
   static int x;
   std::cout << "f ";
   return x;
}

int main()
{
   for (int i = 0; i < 5; ++i)
   {
      int& y = f();
   }   
   return 0;
}
Вот результат запуска:
Так это при каждом обращении к функции, а не к ссылке.

Код: Выделить всё

#include <iostream>

int& f()
{
   static int x;
   std::cout << "f ";
   return x;
}

int main()
{
 int d;
 int w;
   for (int i = 0; i < 5; ++i)
   {
      int& y = f();
    d=y;
   y=w; 
   }   
   return 0;
}
выведет то же самое "f f f f f ", а не "f f f f f f f f f f f f f f f ", как было бы при проверке при каждом обращении к y. А если между int &y=f(); и y=w что то изменилось, из-за чего переменная должна стать не доступной? f()=w; это учтёт, а y=w - нет.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Romeo писал(а):Хардкод значений (в нашем случае 1000000) так же не допустим.
Это был просто пример копирования большого количества данных, в этом случае написать 1000000 лучше, чем заменить его идентификатором, по которому не видно, что это много данных. Если же решается задача, а не просто образ очень длинного цикла, то тогда да, забивать само число нельзя.
Возвращение this по значению из оператора присваивания при том, что каждый объект этого класса весит минимум 4 мегабайта - это просто убийство. Конечно же не хватает амперсанда. Ребята, не делайте так никогда! Подобный код (взависимости от того, насколько он часто вызывается), может замедлить вашу программу в тысячи раз.
Прочитайте ещё раз название темы.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Сионист писал(а):А если между int &y=f(); и y=w что то изменилось, из-за чего переменная должна стать не доступной? f()=w; это учтёт, а y=w - нет.
Сионист, речь идёт не о "недоступности", которая может наступить или не наступить между двумя выдуманными строчками кода, а о разовой проверке или просто дополнительной логике, которую необходимо выполнить перед тем, как выдать указатель наружу. После того, как указатель выдан, ответственность за его использование уже ложится на программиста, вызывающего кода. Ты ведь понимаешь, что эта функция может быть вызвана из разных мест в программе, причём эти места могут располагаться в разных единицах трансляции? Так что функцию в любом случае придётся вызывать из каждого такого места, и дополнительная логика отработает. Не стоит её расценивать, обязательно как проверяющую. Скорее, как pre-get hook. В таком виде подход вполне приемлем. Особенно в С-шных модулях.

Укажу ещё варианты, когда функция, возвращающая ссылку на локальную статическую переменную нам может быть необходима. Сначала зададимся вопросом, а в чём же поведенческое отличие такой функции от глобальной переменной? Ответ очевиден. Разные моменты инициализации. Глобальная переменная будет инициализирована программой до входа в main. Статическая же переменная внутри функции - только в момент первого вызова. Отсюда вытекает два случая, когда указанный подход будет полезен.

Во-первых, если наша переменная - это объект тяжеловесного класса, который в конструкторе делает много работы и, как следствие, тратит много процессорного времени. В этом случае есть смысл, как минимум, отложить выполнение тяжёлого участка кода до того времени, когда он нам действительно понадобится. Это принцип называется lazy loading. Более того, возможна ситуация, что функция вызывается только при каком-то условии. В таком случае мы не просто отложим тяжеловесную операцию, но и вообще сможем её избежать, если не попадём в if.

Во-вторых, будет уместно вспомнить фреймворки, которые требуют обязательного вызова некого init перед тем, как начать с ними работать. Один из самых известных примеров, пожалуй, это COM. Никакие API COM не будут работать до тех пор, пока программа не вызовет CoInitialize. Стоит ли объяснять, почему в случае COM, глобальное создание COM-объекта невозможно? Всё из-за того же времени инициализации. Глобальная переменная инициализируется до вызова main, а CoInitialize вызывается именно в main, то есть позже. Наша же функция, возвращающая указатель на статическую переменную, спасает ситуацию, так как откладывает время инициализации.

В общем подытожу. Подход имеет место быть и его стоит вычеркнуть из списка того, как делать нельзя.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Сионист писал(а):Прочитайте ещё раз название темы.
Я и так похвалил, что ты предоставляешь отличные образчики для того, чтобы показать как делать нельзя. Вот только в остальных случаях ты сразу объясняешь, где лажа, а в этом случае явно предоставил пример того, как должен выглядеть "хороший" код. Так что у меня есть определённые сомнения, что возвращение по значению сделано умышленно. Но это уже Бог с ним :) Главное, что цель достигнута и все счастливы :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Romeo писал(а):Я и так похвалил, что ты предоставляешь отличные образчики для того, чтобы показать как делать нельзя. Вот только в остальных случаях ты сразу объясняешь, где лажа, а в этом случае явно предоставил пример того, как должен выглядеть "хороший" код.
А ничего, что в том же посте написано, что так делать тоже нельзя? Это пример не хорошего кода, не падающей лажи. Просто мне не пришло в голову, что кому то может потребоваться подробный разбор такой лажи.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Так это же отлично, что не пришло в голову, что кто-то будет разбирать. Когда программист пишет не задумываясь о том, что его код будет кто-то читать или разбирать, пишет на одних рефлексах, то именно по такому коду можно оценить истинную культуру его программирования.

Да и вообще, не волнуйся, для этого же тема и была придумана.

Накидай ещё кода - разберём. Можешь не своего ;)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Romeo писал(а):Сионист, речь идёт не о "недоступности", которая может наступить или не наступить между двумя выдуманными строчками кода, а о разовой проверке или просто дополнительной логике
А смысл в разовой проверке? Если проверка не обязательна при каждом обращении к переменной, то она не связана с этой переменной, либо вообще не является проверкой. И в обоих случаях недопустима. Если нужен конструктор, результат работы которого гарантирован, то и пишите конструктор и ничего не проверяйте. А если надо что то проверять, то при каждом обращении.
Romeo писал(а):Так это же отлично, что не пришло в голову, что кто-то будет разбирать. Когда программист пишет не задумываясь о том, что его код будет кто-то читать или разбирать, пишет на одних рефлексах, то именно по такому коду можно оценить истинную культуру его программирования.
Даже представить себе не могу того уникума, который это может написать рефлекторно.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Сионист писал(а):А смысл в разовой проверке?
Я же тебе подчеркнул, что не расценивай это, как проверку. Расценивай это как pre-get hook. Или ты не понимаешь, о чём идёт речь? Всё равно про проверку гнёшь. А ещё я написал, что это особо актуально в С-шных модулях, а ты мне про конструкторы.

А кроме первого абзаца ты ничего не прочёл? Там есть ещё два случая и оба очень веские. И о твоих любимых конструкторах в них тоже упомянуто. Или ты специально эти два случая опускаешь? :)
Сионист писал(а):Даже представить себе не могу того уникума, который это может написать рефлекторно.
Эту ветвь беседы я не буду продолжать. Причём в твоём же интересе :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Во-вторых, будет уместно вспомнить фреймворки, которые требуют обязательного вызова некого init перед тем, как начать с ними работать. Один из самых известных примеров, пожалуй, это COM. Никакие API COM не будут работать до тех пор, пока программа не вызовет CoInitialize. Стоит ли объяснять, почему в случае COM, глобальное создание COM-объекта невозможно? Всё из-за того же времени инициализации. Глобальная переменная инициализируется до вызова main, а CoInitialize вызывается именно в main, то есть позже.
А конструктор глобального объекта когда вызывается?
А ещё я написал, что это особо актуально в С-шных модулях, а ты мне про конструкторы.
Вы ещё бейсик помяните.
Во-первых, если наша переменная - это объект тяжеловесного класса, который в конструкторе делает много работы и, как следствие, тратит много процессорного времени. В этом случае есть смысл, как минимум, отложить выполнение тяжёлого участка кода до того времени, когда он нам действительно понадобится. Это принцип называется lazy loading. Более того, возможна ситуация, что функция вызывается только при каком-то условии. В таком случае мы не просто отложим тяжеловесную операцию, но и вообще сможем её избежать, если не попадём в if.
Явное позднее связывание dll, поставляющей ресурсы, либо возврат оболочки над указателем с перегруженными операторами присваивания и привидения, что дополнительно гарантирует перехват каждого обращения на случай, если кому то придёт в голову к целевому объекту прикрутить ещё и деинит, а потом в одной части программы вызвать его, а в другой обратиться к самому объекту.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Ответить