Пять правил программирования Роба Пайка



Пять правил программирования, написанных Робом Пайком:

You can’t tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don’t try to second guess and put in a speed hack until you’ve proven that’s where the bottleneck is.
  1. Measure. Don’t tune for speed until you’ve measured, and even then don’t unless one part of the code overwhelms the rest.
  2. Fancy algorithms are slow when n is small, and n is usually small. Fancy algorithms have big constants. Until you know that n is frequently going to be big, don’t get fancy. (Even if n does get big, use Rule 2 first.)
  3. Fancy algorithms are buggier than simple ones, and they’re much harder to implement. Use simple algorithms as well as simple data structures.
  4. Data dominates. If you’ve chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.
Роб Пайк участвовал в разработке операционных систем Inferno, Plan 9, а также языков Alef, Limbo, Newsqueak и на данный момент занят языком Go.

Перевод (копипаст с блога http://vladimir-vg.github.com/) этих пяти правил:
  1. Никто не знает какая часть кода будет потреблять больше ресурсов. Узкие места бывает в совершенно неожиданных точках, поэтому не пытайтесь угадывать и как-то оптимизировать код до тех пор пока вы на деле не выяснили что эта часть действительно является узким местом.
  2. Измеряйте. Не пытайтесь оптимизировать пока вы не провели измерений. И даже после этого, не оптимизируйте, пока не убедитесь что эта часть кода тяжелее всего остального.
  3. Нетривиальные алгоритмы работают медленно если достаточно маленькое, и обычно именно так и случается. Зато в них достаточно большие константы сложности. Пока не убедитесь что будет достаточно большим, не усложняйте код нетривиальными алгоритмами. (И даже если достаточно велико, убедитесь что выполняется второе правило)
  4. В нетривиальных алгоритмах гораздо легче ошибиться и их гораздо сложнее реализовать. Используйте простые алгоритмы и простые структуры данных.
  5. Данные важнее кода. Если вы правильно подберёте структуры данных, то код будет почти очевидным. Именно данные являются главной вещью в программировании, а не код.

C++. Спецификатор inline. Заметка

Заглянем в википедию: "
Спецификатор inline позволяет объявлять inline-функции. Функция, определённая внутри тела класса, является inline по умолчанию. Изначально inline-функции задумывались как функции, являющиеся хорошими кандидатами на оптимизацию, при которой в местах обращения к функции компилятор вставит тело этой функции, а не код вызова. В действительности компилятор не обязан реализовывать подстановку тела для inline-функций, но может, исходя из заданных критериев оптимизации, выполнять подстановку тела для функций, которые не объявлены как inline. Пожалуй, наиболее значимой особенностью inline-функции является то, что она может многократно определяться в нескольких единицах трансляции (при этом inline-функция должна быть определена во всех единицах трансляции, где она используется), в то время как функция, не являющаяся inline, может определяться в программе не более одного раза. Пример:
inline double Sqr(double x) {return x*x;}. "

Вследствие абстракции данных члены - функции классов выносят за их пределы, поэтому нужно не забывать описывать такие функции с префиксом inline, но это не панацея, так как компилятор сам решает как именно ему действовать в этой ситуации (в принципе, если функция несложна, то думаю, компилятор даст зеленый свет на активацию inline для сие функций).

C++. Список инициализации членов в конструкторе. Альтернативный синтаксис

Существует альтернативный синтаксис инициализации членов классов используя так называемый список инициализации, в котором через запятую указываются имена и начальные значения. Например, конструктор по умолчанию можно переписать следующим образом:
 
// конструктор по умолчанию класса Test с использованием
// списка инициализации членов int member1, double member2, int member3
 
inline Test:: Test() : member1(0), member2( 0.0 ), member3( 0 ) {};
 
Такой список допустим только в определении, но не в объявлении конструктора. Он помещается между списком параметров и телом конструктора и отделяется двоеточием. Вот как выглядит конструктор с двумя параметрами при частичном использовании списка инициализации членов:
 
inline Test:: Test(int m1, double m2, int m3) : member1(m1), member2(m2), member3(m3) {};
 
Важно помнить о соблюдении порядка объявления членов, которые мы хотим инициализировать через список в конструкторе: порядок объявления должен быть равнозначен порядку инициализации, например:
 
class Test {
    // Порядок объявления членов класса
    int b;
    int a;
public:
    Test(int x) : a(x), b(a) {}; // Нарушен порядок
};
 
При этом компилятор может не проругаться, однако переменные будут содержать неоднозначные значения (мягко говоря). На последок небольшой рабочий пример.
 
#include <iostream>
using namespace std;
 
class Test {
   int a;
   int b;
public:   Test() : a(16), b(61) {};
   //Test(int x) : a(x), b(a) {}; // Нарушен порядок
   void show() {cout << "a = " << a << " b = " << b << endl;};
};
 
int main() {
   Test t;
   //Test t(5); // Для вызова конструктора с нарушенным поядком объявления и инициализации
   t.show();
}

 

C++. Указатели на функции, массивы указателей на функции

Указатели сами по себе очень полезны в работе, но в то же время коваврны, потому что с ними нужно быть очень аккуратным, иначе не избежать утечки памяти. Ниже рассмотрено как можно применять указатели к функциям. Предположим, что у нас есть декларированные функции:

int f1(int); int f2(int); int f3(int);

Тогда можно объявить указатель:

int (*p) (int);

Скобки здесь обязательны так как тогда компилятор воспримет это выражение так:

int* p(int);

Ну а это объявление функции с возвращаемым типом типа указатель. Продолжим.
Теперь, ему можно присвоить ссылку на одну из функций:
 
p = f1;

Если прописать так,

p = f1();

то это приведет к попытке вызова функции и тем самым вызовет ошибку компиляции.
После этого можно вызывать функцию следующим образом:

(*p) (16); // p (16); Так тоже работает

Стоит отметить, что при объявлении указателей на функцию, необходимо соблюдать согласованность типов между указателями и функциями, например так будет некорректно:
 
int (*p) (); // Это равнозначно вырыжению int (*p) (void);

А это уже несогласованность!
Есть еще один нюанс. Предположим, что функция декларирована с аргументами по-умолчанию
 
int f4(int n = 0);

И казалось можно написать так

int (*p) (int);
p = f4;
p ();

И я получаю ошибку компиляции

error C2198: 'int (__cdecl *)(int)' : too few arguments for call

Поэтому нужно будет передать конкретное значение

p (16);

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

template <class T>
T getme(T a, T b) {

       return (a>b?a:b);
}


Тогда при объявлении указателя будет опрелен тип функции getme()
 
char (*tpl) (char, char);
tpl = getme;
cout << "from template function getme() " << tpl ('a', 'b') << endl;


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

typedef int (*F) (int); // Сие эквивалентно выражению int (*) int;

Таким образом мы получили синоним указателей - F. Далее с ним работаем вот так

F p; // Сие эквивалентно выражению int (*p) int;

Так мы объявили указатель на функции, ну а дальше по аналогии

p = f1;
p (16);

Теперь рассмотрим массив указателей на функции. Здесь ничего сложного, итак, объявим сие массив:

int (*pm[]) (int) = {f1, f2, f3};

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

(*pm[0]) (16);

На этом закончу. Если что, то исходник можно скачать здесь.