вторник, 6 сентября 2016 г.

Man shall not live by bread alone.

Интересно, а задавался ли кто-нибудь вопросом о том, почему в описаниях вакансий очень часто в разделе "Необходимые навыки" присутствует пункт типа "Разговорный английский", но при этом в разделе "Компания предлагает" есть строка "Бесплатное обучение английскому"? Может быть имеется в виду то, что "мы разговорим ваш английский еще больше, если вы станете нашим сотрудником"?

Если в списке требований есть что-то наподобие "At least upper — intermediate English level (both written and spoken)", то курсы уместно обещать, но при требовании от кандидата разговорного английского - это выглядит по меньшей мере глупо.

Почему, например, требуя в качестве необходимого навыка экспертные знания C++, никто не предлагает будущему сотруднику курсы по программированию на том же самом С++? А ведь заменив в предыдущем предложении слова "экспертные знания C++" на "Разговорный английский", мы получим, по сути дела, тоже самое. Хотя, если подходить к этому вопросу, руководствуясь тезисом "нет предела совершенству", то - очень может быть...

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

Примечание:
"Man shall not live by bread alone." - (англ. библейское высказывание) Не хлебом единым жив человек.

понедельник, 5 сентября 2016 г.

C++: игра в константность.

Говорят: умней они...
Но что слышим от любого?
Жомини да Жомини!
А о водке — ни полслова!

(Д. Давыдов “Песня старого гусара”)
Вопрос кто такой этот Жомини константности в С++ - тема весьма интересная, на которую пишут все, кому не лень. :) Мне тоже сегодня было не лень, поэтому вот такой простой пример:
class Foo {
private:
    int x;
public:
    void f();
    void g();
};

void h(int*) {};
void m(Foo*) {};
void Foo::g() {};

void Foo::f() {
     x = 1;    
     this->g(); 
     h(&x);     
     m(this);   
}

int main() {
    Foo foo;
    foo.f();

    return 0;
}


$ g++ -Wall test.cpp -o test
В данном случае все компилируется без ошибок, но если сделать метод f() константным, то есть
void f() const;
то каждая из четырех строк в теле метода f() будет вызывать ошибку при компиляции. Почему? А вот поэтому:
x = 1;     // Нельзя: изменяется переменная класса
this->g(); // Нельзя: g – некоторая функция
h(&x);     // Нельзя: h может изменить x
m(this);   // Нельзя: не константный аргумент в m()
Вот так мир устроен.

воскресенье, 28 августа 2016 г.

Глоток нирваны в ночь лисьего дзен.

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

(Аркадий и Борис Стругацкие "Понедельник начинается в субботу")
Иллюзии… Кое-кто утверждает, что причиной наших иллюзий является наше неведение, и если мы от него избавимся, то достигнем просветления. Из-за этого неведения мы обречены собственной деятельностью на бесконечный круговорот перерождений в феноменальном мире, который сам есть иллюзия.

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

Если предположить, что все именно так, то почему мы видим слона как слона? Воспринимает ли другое существо слона как слона?

Из-за узкой полосы невысоких кустов, поднимавшейся за нашими спинами, неожиданно вышел огромный белый слон. Он появился именно из-за кустов, хотя по высоте был раз в десять их выше, и я совершенно не в силах объяснить, как это произошло. Не то чтобы он был маленьким в тот момент, когда появился, а потом, приближаясь к нам, вырос в размерах в несколько раз. И не то чтобы он вышел из-за какой-то невидимой стены, совпадавшей по своему расположению с этими кустами. Выходя из-за кустов, слон уже был неправдоподобно огромным, и вместе с тем он вышел именно из-за крохотной полоски кустов, за которой вряд ли могла бы спрятаться и овца[1].

Различные виды соприкосновения наших органов чувств, развитых у каждого в той или иной степени, зависящие от множества факторов, вызывают и различные чувства. Исходя из этого, масса дхарм (в смысле субатомных частиц и сил), предстающая перед нами в облике слона, для другого существа может предстать в совершенно ином облике. Различные виды чувствований ведут в свою очередь к различным видам желаний. Желание вызывает привязанность. Чем больше чего-то желаешь, тем больше становишься зависимым. Если индивидуум так и не обрел просветления, то его неведение продолжит круговорот зависимого происхождения, подтверждая тем самым существование одноименного закона. Дхармы окружают каждого из нас повсеместно и проявляют себя как воспринимаемые нами объекты и процессы только в случае, когда мы к ним привязаны. Ситуация усугубляется тем, что воспринимаемое пронизывает привычная нам идея полярности добра и зла, столь характерная для культуры Запада и христианской морали. Восток совершенно иначе, чем это изложено в Ветхом и Новом завете, взирает как на земную жизнь, так и на посмертное существование.

Со мной повторилось то же самое, что и несколько минут назад, - мне показалось, что вот-вот я пойму что-то очень важное, что вот-вот станут видны спрятанные за покровом реальности рычаги и тяги, которые приводят в движение все вокруг. Но это чувство прошло, а огромный белый слон остался перед нами[1].

Если полагать, что наши знания о внешнем мире, в котором тут кое-кто попытался столь нахально усомниться, мы получаем исключительно на основе наших органов чувств, то уверенность свою мы можем основывать только на данных наших ощущений. Когда мы видим слона, все, что мы на самом деле знаем, состоит в том, что наш ум получил информацию от органов чувств, которая согласуется с представлением о слоне. Гейзенберг (не захотелось мне опять употреблять слова “квантовая теория”) пошел еще дальше, утверждая, что настоящее существование окружающего нас мира или его текущее состояние строго детерминировано тем обстоятельством, что сознающий ум его сейчас наблюдает.

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

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

Ты человек образованный, знаешь, наверно, есть в Индии такая древняя книга - Ебанишада[1]. Так вот, в этой книге сказано:

Неразумные следуют внешним желаниям, они попадают в распростертые сети смерти.
Мудрые же, узрев бессмертие, не ищут здесь постоянного среди непостоянных вещей.


Ну как, достиг просветления и освободился от своего лисьего облика? Еще нет? Ну “Тогда пойди и вымой свою чашку.”[2]

А со слоном что делать? Слона можете взять себе. :)

Примечания
1. Виктор Пелевин “Чапаев и Пустота”
2. “Кости и плоть дзен.”

суббота, 14 мая 2016 г.

"Ну, Кисуля, а в каких пределах вы знаете немецкий язык?"

Месье, же не манж па сис жур. Гебен зи мир битте этвас копек ауф дем штюк брод. Подайте что-нибудь бывшему депутату Государственной думы.

(И.Ильф, Е.Петров "Двенадцать стульев")
Казалось бы, а с чего это вдруг зашла речь о немецком языке? Ну а почему бы и нет? Я ведь не обещал денно и нощно глаголить исключительно на компьютерные темы.
В памяти сохранились чьи-то слова о том, что хороший программист - это в первую очередь хороший лингвист, а уж потом математик. Мне с этой мыслью сложно не согласиться, да и большинство программируемых мною задач было связанно именно с манипулированием данными, а не с математическими вычислениями. Но оставим пока в покое программирование, особенно сегодня, в пятницу 13-го числа, плавно перетекающую в субботу.
Для тех, кто свои познания в немецком поддерживает пока на уровне виртуозного владения фразой "Ja-ja, das ist fantastisch!", этот пост может показаться скучным и малоинтересным, хотя, кто знает, может именно после его прочтения кто-нибудь вдруг надумает взять учебник немецкой грамматики, проникнуться языком Шиллера и Гете, а потом... О-о, это я малость погорячился.
Как мне кажется, написать этот пост меня сподвиг Борис Николаевич Климзо и его замечательная книга "Ремесло технического переводчика". Благодаря плеяде прекрасных переводчиков у нас есть возможность наслаждаться произведениями зарубежных авторов, читая их иногда в оригинале, а чаще в переводе на русский язык. Произведения Рэя Брэдбери, Клиффорда Саймака, Айзека Азимова, Артура Кларка, Роджера Желязны, Урсулы Ле Гуин и Роберта Шекли когда-то для меня открыла Нора Галь...
Чтобы размять уставший от программистских задач мозг, я решил на досуге более пристально взглянуть на то, как я сам осуществляю анализ предложений переводимого текста. Я заметил, что при чтении технического текста знание правил грамматики присутствует где-то на уровне фона, выходя на передний план только в случае, если технический смысл прочитанного ускользяет из-за грамматической витиеватости и незнакомых слов. При работе с беллетристикой грамматика играет первую скрипку, ибо смысловые отенки здесь весьма существенны.

Возьмем для примера фрагмент из Drei Kameraden, Erich Maria Remarque и подчеркнем в предложениях сказуемые:

Der Himmel war gelb wie Messing und noch nicht verqualmt vom Rauch der Schornsteine. Hinter den Dächern der Fabrik leuchtete er sehr stark. Die Sonne mußte gleich aufgehen. Ich sah nach der Uhr. Es war noch vor acht. Eine Viertelstunde zu früh.

Данный фрагмент вряд ли вызовет проблемы при переводе.

Небо было желтым, как латунь: его еще не закоптило дымом. За крышами фабрики оно светилось особенно сильно. Вскоре должно было взойти солнце. Я посмотрел на часы. До восьми часов оставалась еще четверь часа.

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

Ich schloß das Tor auf und machte die Benzinpumpe fertig. Um diese Zeit kamen immer schon ein paar Wagen vorbei, die tanken wollten.

Я открыл ворота и подготовил насос бензоколонки. Всегда в это время уже подъезжали заправляться первые машины.

А вот тут уже чуть сложнее: здесь присутствует отделяемая приставка auf глагола aufschließen, не обратив внимание на которую, можно получить совершенно противоположное действие - schließen. Кроме того, сказуемое придаточного предложения содержит имперфект модального глагола wollen (хотеть) в третьем лице множественного числа, которое в сочетании с die, подсказывает, что подлежащее главного предложения представлено множественным числом - ein paar Wagen (дословно: пара машин).

Далее становится еще интереснее:

Plötzlich hörte ich hinter mir ein heiseres Krächzen, das klang, als ob unter der Erde ein rostiges Gewinde hochgedreht würde.

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

Ключом к правильному пониманию является hochgedreht würde. Здесь и пассивный залог, и кондиционалис.

1918. Das war im Lazarett. Ein paar Tage vorher war ein neuer Transport angekommen. Papierverbände. Schwere Verletzungen. Den ganzen Tag fuhren die flachen Operationswagen herein und hinaus. Manchmal kamen sie leer wieder. Neben mir lag Josef Stoll. Er hatte keine Beine mehr, aber er wußte es noch nicht. Es war nicht zu sehen, weil die Decke über einem Drahtkorb lag. Er hätte es auch nicht geglaubt, denn er spürte Schmerzen in den Füßen.

Это было в госпитале. Двумя днями раньше прибыла новая партия раненых. Тяжелые ранения. Повязки из бумажных бинтов. Стоны. Весь день то въезжали, то выезжали длинные операционные тележки. Иногда они возвращались пустыми. Рядом со мной лежал Йозеф Штоль. Ног у него у же не было, но он об этом еще не знал. Увидеть он не мог, потому что там, где должны были лежать его ноги, торчал проволочный каркас, покрытый одеялом. Да он и не поверил бы, потому что чувствовал боль в ногах.

Речь идет о событиях, которые произошли в прошлом, еще до момента повествования, то есть прошедшее в прошедшем, выраженное при помощи сказуемого в плюсквамперфекте (Das Plusquamperfekt): war angekommen и hätte geglaubt. Причем последнее - это плюсквамперфект конъюнктива (hätte - глагол haben в имперфекте конъюнктива и партицип II смыслового глагола).

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

»Na, das ist nun eine Übertreibung. Sie sind nur voll. Voll wie eine Strandhaubitze.«

Интересует вот это "Voll wie eine Strandhaubitze." Смысл выражения чем-то схож с русским "Быть пьяным в стельку." Дословный перевод весьма примечателен "Быть пьяным как гаубица на пляже". Это выражение встречается не только у Ремарка. Вот еще более красочный пример из Das Boot, Lothar-Günther Buchheim :

"Steckn bloss nicht daneben. Du bist ja blau wie ne Strandhaubitze!" (...Ты уже нажрался.)

Пожалуй, что на этом и хватит на сегодня, а то ich bin so gerührt – darauf muß ich unbedingt noch einen zwitschern. :)

вторник, 26 апреля 2016 г.

Паттерн "Наблюдатель"

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

(Аркадий и Борис Стругацкие "Понедельник начинается в субботу")
До вчерашнего дня мне казалось, что тема паттернов, ненавязчиво затронутая в Паттерн "Стратегия", нашла свое логическое завершение в Паттерн "Стратегия": жаркое из питона. Похоже на то, что я заблуждался, ибо интерес к этой теме продолжал тлеть все это время и проявился в виде просьбы продолжить ее дальше.
Один из моих читателей, интересующийся C++, вернее, пытающийся вглядываться в горизонты C++, опираясь на знания Java, предпринял самостоятельную попытку переписать на C++ код паттерна "Наблюдатель" из книги Head First Design Patterns By Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra, но в ходе этой работы у него возникли затруднения, которые мы сегодня постараемся разрешить.
Изучение C++ таким способом безусловно содержит в себе рациональное зерно и вполне может оказаться, что такой подход к изучению нового языка даст неплохие результаты.
Ну что же, давайте попробуем написать код паттерна "Наблюдатель" на C++, сохраняя, по возможности, некоторую общность с имеющейся реализацией на Java.
#include <iostream>
#include <vector>
#include <algorithm>

class Observer {
public:
    virtual void update(float temp, float humidity, float pressure) = 0;
};


class Subject {
public:
    virtual void registerObserver(Observer *obj) = 0;
    virtual void removeObserver(Observer *obj) = 0;
    virtual void notifyObservers() = 0;
};


class WeatherData: public Subject {
    std::vector<Observer *> observers;
    float temperature;
    float humidity;
    float pressure;
public:
    void registerObserver(Observer *obj) { 
         observers.push_back(obj);
         std::cout << "Observers: " << observers.size() << std::endl;
    }

    void removeObserver(Observer *obj) {
        observers.erase(std::remove(observers.begin(), observers.end(), obj), observers.end());
    }

    void notifyObservers() {
        std::vector<Observer *>::const_iterator cii;
        for (cii=observers.begin(); cii!=observers.end(); cii++) {
            (*cii)->update(temperature, humidity, pressure);
       }
    }

    void measurementsChanged() { notifyObservers(); }

    void setMeasurements(float temperature, float humidity, float pressure) {
        this->temperature = temperature;
        this->humidity = humidity;
        this->pressure = pressure;
        measurementsChanged();
    }

    float getTemperature() { return temperature; }
    float getHumidity() { return humidity; }
    float getPressure() { return pressure; }
};


class DisplayElement {
public:
    virtual void display() = 0;
};


class CurrentConditionsDisplay: public Observer, DisplayElement {
    float temperature;
    float humidity;
    Subject *weatherData;
public:
    CurrentConditionsDisplay(Subject *weatherData) {
        this->weatherData = weatherData;
        weatherData->registerObserver(this);
    }

    void update(float temperature, float humidity, float pressure) {
        this->temperature = temperature;
        this->humidity = humidity;
        display();
    }

    void display() {
        std::cout << "Current conditions: " << temperature 
                  << "F degrees and " << humidity << "% humidity" << std::endl;
    }
};


class StatisticsDisplay: public Observer, DisplayElement {
    float maxTemp;
    float minTemp;
    float tempSum;
    int numReadings;
    WeatherData *weatherData;
public:
    StatisticsDisplay(WeatherData *weatherData) {
        this->weatherData = weatherData;
        weatherData->registerObserver(this);
        maxTemp = 0.0f;
        minTemp = 200;
        tempSum = 0.0f;
        numReadings = 0;
    }

    void update(float temp, float humidity, float pressure) {
        tempSum += temp;
        numReadings++;

        if (temp > maxTemp) { maxTemp = temp; }
        if (temp < minTemp) { minTemp = temp; }

        display();
    }

    void display() {
        std::cout << "Avg/Max/Min temperature = " << (tempSum / numReadings)
                  << "/" << maxTemp << "/" << minTemp << std::endl;
    }
};


class ForecastDisplay: public Observer, DisplayElement {
    float currentPressure;  
    float lastPressure;
    WeatherData *weatherData;
public:
    ForecastDisplay(WeatherData *weatherData) {
        this->weatherData = weatherData;
        weatherData->registerObserver(this);
        currentPressure = 29.92f;
    }

    void update(float temp, float humidity, float pressure) {
        lastPressure = currentPressure;
        currentPressure = pressure;

        display();
    }

    void display() {
        std::cout << "Forecast: " << std::endl;
        if (currentPressure > lastPressure) {
            std::cout << "Improving weather on the way!" << std::endl;
        } else if (currentPressure == lastPressure) {
            std::cout << "More of the same" << std::endl;
        } else if (currentPressure < lastPressure) {
            std::cout << "Watch out for cooler, rainy weather" << std::endl;
        }
    }
};


int main() {
    WeatherData weatherData;

    CurrentConditionsDisplay currentDisplay(&weatherData);
    StatisticsDisplay statisticsDisplay(&weatherData);
    ForecastDisplay forecastDisplay(&weatherData);

    weatherData.setMeasurements(80, 65, 30.4f);
    weatherData.setMeasurements(82, 70, 29.2f);
    weatherData.setMeasurements(78, 90, 29.2f);
}


$ g++ -Wall weather.cpp -o weather
$ ./weather.exe
Observers: 1
Observers: 2
Observers: 3
Current conditions: 80F degrees and 65% humidity
Avg/Max/Min temperature = 80/80/80
Forecast:
Improving weather on the way!
Current conditions: 82F degrees and 70% humidity
Avg/Max/Min temperature = 81/82/80
Forecast:
Watch out for cooler, rainy weather
Current conditions: 78F degrees and 90% humidity
Avg/Max/Min temperature = 80/82/78
Forecast:
More of the same
И под занавес замечу, что заслуживают более пристального внимания пара нюансов. А именно, тело метода
void removeObserver(Observer *obj)
Здесь присутствует "Erase–remove idiom", на которую стоит обратить свой взор и ознакомиться с ней поближе. Тело метода
void notifyObservers()
можно написать несколько иначе, если использовать C++ версии 11:
for (const auto& i: observers) { i->update(temperature, humidity, pressure); }
Команда вызова компилятора в этом случае:
g++ -Wall -std=c++11 weather.cpp -o weather
И еще... Ну о "еще" поговорим в другой раз по мере появления новых вопросов.

четверг, 7 апреля 2016 г.

Паттерн "Стратегия": жаркое из питона.

Разумеется, нарисовал я вовсе не шляпу. Я изобразил удава, который переваривает слона. Но взрослые никак не могли этого уразуметь, и тогда я сделал еще один рисунок: я нарисовал внутренности боа-констриктора, да так, чтобы взрослые уж наверняка все разглядели. Взрослым вечно надо растолковывать, что к чему.

(Антуан де Сент-Экзюпери "Маленький принц")
Этот пост является продолжением темы, начатой в Паттерн "Стратегия".
Поводом для его написания стали вопросы, которые возникли у двоих моих читателей после изучения раздела 3.9 "Паттерн Стратегия" книги Марка Саммерфилда "Python на практике" издательства "ДМК Пресс".
Вероятно, что еще одной причиной для продолжения обсуждения также послужила моя фраза, позаимствованная у И.Ильфа и Е.Петрова, слегка переиначенная, и как-то оброненная в ходе разговора, "Саммерфилд - это голова!.."
Для тех, кто еще не читал этой книги и этого раздела, я очень кратко изложу суть дела. В самом начале раздела Саммерфилд справедливо утверждает: "Паттерн Стратегия позволяет инкапсулировать набор взаимозаменяемых алгоритмов, из которых пользователь выбирает тот, что ему нужен."
Далее в разделе 3.9 приведены два разных алгоритма организации списка с произвольным количеством элементов в виде таблицы с указанным числом строк. Один алгоритм выводит фрагмент HTML-разметки, а другой порождает результат в виде простого текста.
Параметризовать алгоритм можно разными способами. Очевидный подход - создать класс Layout, который принимает экземпляр Tabulator, который строит ту или иную таблицу. Пример в файле tabulator1.ру. Это решение можно улучшить, используя табуляторы без состояния, имеющие только статические методы, и передавать алгоритму класс, а не экземпляр табулятора. Пример в файле tabulator2.ру.
В этом разделе показан более простой, но при этом еще более удачный (по словам автора) прием: класс Layout, который принимает в виде параметра функцию табуляции, реализующую нужный алгоритм. Пример в файле tabulator4.ру. Упомянутые py-файлы можно скачать отсюда.

В свете того, о чем шла речь в начальном посте Паттерн "Стратегия", появился вопрос, смысл которого сводился к следующему. Так как общеизвестен факт, что на скриптовом языке, создавать программы проще, чем на языках со статической типизацией, то насколько сложнее воплотить паттерн "Стратегия" для рассматриваемой задачи на C++ по сравнению с реализацией на Python, предложенной в книге Саммерфилда.
В виду того, что реализация алгоритмов вывода списка в виде html или плоского текста в данном конкретном случае является только "гарниром", поэтому на ней останавливаться не буду, а покажу сам каркас - отражу в коде только те элементы решения, которые относятся к сути рассматриваемого вопроса.
#include <iostream>
#include <vector>
#include <string>

class Tabulator {
public:
    virtual void tabulate (int,  std::vector&) = 0;
};

class HtmlTabulator: public Tabulator {
public:
    void tabulate(int rows,  std::vector& items ) {.
        std::cout << "\thtml:" << rows << std::endl;.
        std::vector::const_iterator cii;
        for (cii=items.begin(); cii!=items.end(); cii++) {
            std::cout << *cii << std::endl;
        }
    }
};

class TextTabulator: public Tabulator {
public:
    void tabulate(int rows,  std::vector& items) {.
        std::cout << "\ttext:" << rows << std::endl;.
        std::vector::const_iterator cii;
        for (cii=items.begin(); cii!=items.end(); cii++) {
            std::cout << *cii << std::endl;
        }
    }
};

class Layout {
    Tabulator *tabulator;
public:
    Layout(Tabulator *pt) { tabulator = pt; }
    void tabulate(int rows,  std::vector& items) { tabulator->tabulate(rows, items); }
};

int main() {
    std::vector WINNERS = {
        "Nikolai Andrianov", "Matt Biondi", "Bjørn Dæhlie",
        "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis",
        "Michael Phelps", "Mark Spitz", "Jenny Thompson"};

    HtmlTabulator htmltab;
    TextTabulator texttab;

    /* возможный вариант:
    Tabulator *ptab;
    ptab = &htmltab;
    ptab->tabulate(12, WINNERS);
    ptab = &texttab;
    ptab->tabulate(14, WINNERS);
    */

    Layout htmllayout(&htmltab);
    htmllayout.tabulate(12, WINNERS);
    Layout textlayout(&texttab);
    textlayout.tabulate(14, WINNERS);

    return 0;
}


$ g++ -g -Wall -std=c++11 tab4.cpp -o tab4
$ ./tab4
        html:12
Nikolai Andrianov
Matt Biondi
Bjørn Dæhlie
Birgit Fischer
Sawao Kato
Larisa Latynina
Carl Lewis
Michael Phelps
Mark Spitz
Jenny Thompson
        text:14
Nikolai Andrianov
Matt Biondi
Bjørn Dæhlie
Birgit Fischer
Sawao Kato
Larisa Latynina
Carl Lewis
Michael Phelps
Mark Spitz
Jenny Thompson
Вариантов здесь можно предложить несколько. Для полноты картины ниже я приведу еще один, в котором передам функцию в качестве параметра:
#include <iostream>
#include <vector>
#include <string>

void html_tabulator(int rows,  std::vector& items) {
    std::cout << "\thtml:" << rows << std::endl;
    std::vector::const_iterator cii;
    for (cii=items.begin(); cii!=items.end(); cii++) {
        std::cout << *cii << std::endl;
    }
}

void text_tabulator(int rows,  std::vector& items) {
    std::cout << "\ttext:" << rows << std::endl;
    std::vector::const_iterator cii;
    for (cii=items.begin(); cii!=items.end(); cii++) {
        std::cout << *cii << std::endl;
    }
}

class Layout2 {
public:
    void (*tabulate)(int rows, std::vector& items);
    Layout2(void (*items)(int rows, std::vector& items)) {
        tabulate = items;
    }
};

int main() {
    std::vector WINNERS = {
        "Nikolai Andrianov", "Matt Biondi", "Bjørn Dæhlie",
        "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis",
        "Michael Phelps", "Mark Spitz", "Jenny Thompson"};

    Layout2 htmllayout(html_tabulator);
    htmllayout.tabulate(12, WINNERS);

    Layout2 textlayout(text_tabulator);
    textlayout.tabulate(14, WINNERS);

    return 0;
}
Экранный вывод будет в точности такой же, как и в первом варианте.

Полагаю, что решение на C++ является ничуть не сложнее решения на Python. Книгу Марка Саммерфилда "Python на практике" рекомендую почитать. Интересная и стоящая книга.
"Саммерфилд - это голова!"

четверг, 31 марта 2016 г.

Oracle C++ Call Interface: перфоманс в бассейне.

- Вперед! - ответил Остап. - Не нойте, старик. Вас ждут золотые челюсти, толстенькая вдовушка и целый бассейн кефира.

(И.Ильф, Е.Петров "Золотой теленок")
(Примечание: "Перфоманс в бассейне" это игра слов, своего рода литературный приём с использованием в одном контексте разных значений одних и тех же слов английского языка - "performance" и "pool".)

Идя на поводу у пожеланий трудящихся, вернувшихся сюда за добавкой, и развивая тему предыдущего поста "Oracle C++ Call Interface: экспресс-курс молодого шамана", необходимо несколько слов сказать об эффективности (performance).

В многопотоковых программах, в которых каждый поток существует относительно короткий промежуток времени, открытие соединения с базой для каждого потока может оказаться неэффективным решением, снижающим быстродействие. Для более эффективного использования ресурсов применяется пул соединений (Connection Pool) - некий ограниченный набор соединений, доступный для множества потоков. Для этого используется специальный метод createConnectionPool(). Как это все выглядит в коде?

Модифицируем пример из предыдущего поста, опустив повторяющиеся и несущественные для рассмотрения фрагменты кода:
class ProfilePool {
private:
    Environment *env;
    Connection *conn;
    ConnectionPool *connPool;
    Statement *stmt;
public:
    ProfilePool(const string user, const string passwd, const string db) {
        env = Environment::createEnvironment(Environment::DEFAULT);
        unsigned int maxConn = 5;
        unsigned int minConn = 3;
        unsigned int incrConn = 2;

        try {
            connPool = env->createConnectionPool(
                user,   // The owner of the connection pool
                passwd, // The password to gain access to the connection pool
                db,     // The database name that specifies the database server 
                        // to which the connection pool is related
                minConn, maxConn, incrConn);
            cout << ((connPool) ? "SUCCESS" : "FAILURE") << " - createConnectionPool" << endl;            
        
            conn = connPool->createConnection(user, passwd);
            cout << ((conn) ? "SUCCESS" : "FAILURE") << " - createConnection" << endl;
        } 
        catch (SQLException ex) {
            cout << "Exception thrown for createConnectionPool" << endl;
            cout << "Error number: " <<  ex.getErrorCode() << endl;
            cout << ex.getMessage() << endl;
        }        
    }
  
    ~ProfilePool() {
        connPool->terminateConnection(conn);
        env->terminateConnectionPool(connPool);    
        Environment::terminateEnvironment(env);
    }

    void displayElements(const int rows = 5)  {
...
    }
};

int main(void) {
...
}


me@myhost ~/instantclient_11_2/sdk/demo $ ./profpool
SUCCESS - createConnectionPool
SUCCESS - createConnection

PROFILEID  NAME                               DESCRIPTION                        BEH
========== ================================== ================================== ===
4160229888 RBS SuperChannel Circiut Profile   RBS SuperChannel Circiut Profile     0
4366380032 Marconi Logic w/o MV               n/a                                  0
4514160128 1. ECI LOGIC                       n/a                                  0
6028239872 AUX SDH-Egate Circuit LOT          AUX SDH-Egate Circuit LOT profile    0
6032679936 AUX SDH-Egate Circuit LOP          AUX SDH-Egate Circuit LOP profile    0

Done.
me@myhost ~/instantclient_11_2/sdk/demo $ 
Отдельного упоминания заслуживают три параметра minConn, maxConn, incrConn метода createConnectionPool(). minConn - это минимальное количество открытых соединений в пуле, которое создается при вызове данного метода. Дополнительные соединения открываются по мере надобности (когда все открытые соединения заняты) с шагом incrConn, но при этом общее количество открытых соединений не должно превышать значения параметра maxConn. Вероятно, разность maxConn и minConn должна быть кратна incrConn. Oracle скуп на рекомендации по выбору этих значений и ограничивается только упоминанием о том, что maxConn и incrConn должны быть больше или равны 1 и Generally, minConn should be set to the number of concurrent statements the application is expected to run, хотя здесь присутствует возможность тюнинга путем динамического конфигурирования значений этих атрибутов и автоматического закрытия соединений по таймауту.
Кстати, внутри одного Environment можно создать несколько пулов соединений, что может оказаться полезным для приложений, нуждающихся в load balancing.

Здесь есть еще много интересного, чем хотелось бы поделиться, и возможно, что время для этого когда-нибудь удастся найти. А пока в качестве дижестива могу предложить почитать: Optimizing Performance of C++ Applications.

среда, 23 марта 2016 г.

Oracle C++ Call Interface: экспресс-курс молодого шамана.

Бендер, – захрипел он вдруг, – вы знаете, как я вас уважаю, но вы ничего не понимаете! Вы не знаете, что такое гусь! Ах, как я люблю эту птицу! Это дивная, жирная птица, честное благородное слово. Гусь! Бендер! Крылышко! Шейка! Ножка! Вы знаете, Бендер, как я ловлю гуся? Я убиваю его, как тореадор – одним ударом. Это опера, когда я иду на гуся! «Кармен»!..

(И.Ильф, Е.Петров "Золотой теленок")
Бытует распространенное мнение, я бы даже назвал это предубеждением, дескать C++ - это безумно сложно, на этом языке трудно создавать программы и т.п. Более того, с чьей-то подачи в народ ушла мысль о том, что на C++ написать программу-клиент для Oracle - это задача вообще из области шаманизма. Особенно огорчают подобные высказывания от взрослых программистов, которые довольно долго трудились на C++, а теперь программируют на скриптах. Неужели на самом деле это такая неподъемная задача, требующая каких-то запредельных знаний? Попробуем проверить.
Для начала неплохо было бы прочитать C++ Call Interface Programmer's Guide, хотя если понять то, что написано в этой документации, то дальше этот пост можно не читать. :)

Когда-то в школе задача по физике начиналась со слова "Дано". С него мы и начнем.
Дан сервер, на котором установлена база данных:
SELECT * FROM v$version;

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
"CORE 11.2.0.3.0 Production"
TNS for Linux: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
В нашем распоряжении есть установленный Linux CentOS release 6.6 (Final) и есть необходимые инструменты:
me@myhost /etc $ uname -a
Linux myhost 2.6.32-573.3.1.el6.centos.plus.x86_64 #1 SMP Fri Aug 14 01:00:05 UTC 2015 
x86_64 x86_64 x86_64 GNU/Linux

me@myhost ~ $ which make
/usr/bin/make
me@myhost ~ $ which g++
/usr/bin/g++
me@myhost ~ $ g++ --version
g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11)
Copyright (C) 2010 Free Software Foundation, Inc.
...
Нам понадобится пара библиотек, которые можно загрузить с сайта Oracle Instant Client Downloads for Linux x86-64:
  • Instant Client Package – Basic Lite: instantclient-basiclite-linux.x64-11.2.0.3.0.zip
  • Instant Client Package - SDK: instantclient-sdk-linux.x64-11.2.0.3.0.zip
me@myhost ~ $ ls -l
-rw-rw-r--. 1 me me 23664653 Mar 22 14:31 instantclient-basiclite-linux.x64-11.2.0.3.0.zip
-rw-rw-r--. 1 me me   641586 Mar 22 14:29 instantclient-sdk-linux.x64-11.2.0.3.0.zip
Распакуем zip-файлы в каталог instantclient_11_2 и для удобства сборки создадим пару линков, и установим несколько переменных окружения:
me@myhost ~/instantclient_11_2 $ ln -s ./libclntsh.so.11.1 ./libclntsh.so
me@myhost ~/instantclient_11_2 $ ln -s ./libocci.so.11.1  ./libocci.so
me@myhost ~/instantclient_11_2 $ cat ../oic11.env
export ORACLE_BASE=/home/me
export ORACLE_HOME=$ORACLE_BASE/instantclient_11_2 
export LD_LIBRARY_PATH=$ORACLE_HOME 
export PATH=$ORACLE_HOME:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:~/bin
Вот и все подготовительные операции.

Итак, у нас есть база данных, в которой есть таблица.
CREATE TABLE PROFILE (
  PROFILEID NUMBER NOT NULL, 
  NAME VARCHAR2(50 CHAR) NOT NULL, 
  DESCRIPTION VARCHAR2(2000 CHAR), 
  BEHAVIOUR NUMBER NOT NULL 
) 
Первые 5 строк этой таблицы мы хотим прочитать.
Вот этот код на C++ помещаем в файл с именем profile.cpp:
#include <iostream>     // std::cout, std::endl
#include <iomanip>      // std::setw
#include <occi.h>

using namespace oracle::occi;
using namespace std;

class Profile {
private:
    Environment *env;
    Connection *conn;
    Statement *stmt;
public:
    Profile(string user, string passwd, string db) {
        // DEFAULT mode is used for creating an Environment object; 
        // it has no thread safety or object support.
        env = Environment::createEnvironment(Environment::DEFAULT);
        conn = env->createConnection(user, passwd, db);
        cout << "Oracle Client Version: " << OCCI_MAJOR_VERSION << 
             "." << OCCI_MINOR_VERSION << endl;
        int majorVersion, minorVersion, updateNum, patchNumber, portUpdateNum;
        env->getClientVersion(majorVersion, minorVersion, updateNum, patchNumber, portUpdateNum);
        cout << majorVersion << "." << minorVersion << "." << updateNum << 
             "." << patchNumber << "." << portUpdateNum << endl;
        const string version = conn->getServerVersion();
        cout << version << endl; // version of the database server
    }

    ~Profile () {
        env->terminateConnection(conn);
        Environment::terminateEnvironment(env);
    }

    void displayElements(const int rows = 5) {
        string sqlStmt = "select PROFILEID, NAME, DESCRIPTION, BEHAVIOUR \
        from PROFILE where ROWNUM <= :x";
        /* In this SQL statement, x is a placeholder for a value that 
           will be supplied by the application. */
        stmt = conn->createStatement(sqlStmt);
        stmt->setInt (1, rows);
        ResultSet *rset = stmt->executeQuery();

        cout << endl << setw(11) << left << "PROFILEID" 
             << setw(35) << left << "NAME" 
             << setw(35) << left << "DESCRIPTION" 
             << setw(3) << left << "BEH"                
             << endl; 
        cout << setw(11) << left << "========== " 
             << setw(35) << left << "================================== " 
             << setw(35) << left << "================================== " 
             << setw(3) << left << "===" 
             << endl;             
        try {
            cout.precision(10);
            while (rset->next ()) {
                float profileid = rset->getFloat(1);
                string name = rset->getString(2);
                string description = (rset->isNull(3) ? "n/a" : rset->getString(3)); 
                string behav = rset->getString(4);
                                  
                cout << setw(11) << left << profileid
                     << setw(35) << left << name
                     << setw(35) << left << description 
                     << setw(3) << right << behav
                     << endl; 
            }
        } 
        catch (SQLException ex) {
            cout << "Exception thrown for displayElements" << endl;
            cout << "Error number: " << ex.getErrorCode() << endl;
            cout << ex.getMessage() << endl;
        }

        stmt->closeResultSet(rset);
        conn->terminateStatement(stmt);
    }

}; // end of class Profile

int main() {
    const string user = "USER";
    const string passwd = "PASSWORD";
    const string db = "10.10.10.10:1521/myservice";      // host:port/service_name
        
    try {
        Profile *demo = new Profile (user, passwd, db);
        demo->displayElements();
        delete (demo);
    }
    catch (SQLException ex) {
        cout << ex.getMessage() << endl;
    }
    cout << endl << "Done." << endl;
    return 0;
}
Устанавливаем необходимое окружение и делаем сборку:
me@myhost ~/instantclient_11_2/sdk/demo $ . ../../../oic11.env
me@myhost ~/instantclient_11_2/sdk/demo $ cat Makefile
profile:profile.cpp
    g++ -Wall -o profile profile.cpp -I$(ORACLE_HOME)/sdk/include -L$(ORACLE_HOME) -lclntsh 
-lnnz11 -locci

clean:
    rm -f profile

me@myhost ~/instantclient_11_2/sdk/demo $ make profile
g++ -Wall -o profile profile.cpp -I/home/me/instantclient_11_2/sdk/include 
-L/home/me/instantclient_11_2 -lclntsh -lnnz11 -locci
me@myhost ~/instantclient_11_2/sdk/demo $ ldd profile
        linux-vdso.so.1 =>  (0x00007fff47ffa000)
        libclntsh.so.11.1 => /home/me/instantclient_11_2/libclntsh.so.11.1 (0x00007f0f337c1000)
        libnnz11.so => /home/me/instantclient_11_2/libnnz11.so (0x00007f0f333f4000)
        libocci.so.11.1 => /home/me/instantclient_11_2/libocci.so.11.1 (0x00007f0f3319d000)
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x000000396c000000)
        libm.so.6 => /lib64/libm.so.6 (0x0000003e95600000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003e96e00000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003e94a00000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003e94e00000)
        libdl.so.2 => /lib64/libdl.so.2 (0x0000003e94600000)
        libnsl.so.1 => /lib64/libnsl.so.1 (0x00007f0f32f76000)
        libaio.so.1 => /lib64/libaio.so.1 (0x00007f0f32d74000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003e94200000)
где
libclntsh.so.11.1 - Client Code Library
libnnz11.so - Security Library
libocci.so.11.1 - OCCI Library

Подробности здесь.
me@myhost ~/instantclient_11_2/sdk/demo $ ls -l profile
-rwxrwxr-x. 1 me me 18269 Mar 22 16:21 profile
-rw-rw-r--. 1 me me  2859 Mar 22 16:21 profile.cpp

me@myhost ~/instantclient_11_2/sdk/demo $ ./profile
Oracle Client Version: 11.2
11.2.0.3.0
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

PROFILEID  NAME                               DESCRIPTION                        BEH
========== ================================== ================================== ===
4160227384 RBS SuperChannel Circiut Profile   RBS SuperChannel Circiut Profile     0
4366384199 Marconi Logic w/o MV               n/a                                  0
4514162428 1. ECI LOGIC                       n/a                                  0
6028237120 AUX SDH-Egate Circuit LOT          AUX SDH-Egate Circuit LOT profile    0
6032681277 AUX SDH-Egate Circuit LOP          AUX SDH-Egate Circuit LOP profile    0

Done.
me@myhost ~/instantclient_11_2/sdk/demo $
А теперь слегка "накосячим" в коде, чтобы посмотреть на исключение. Для этого заменим строку
float profileid = rset->getFloat(1);
на
unsigned int profileid = rset->getUInt(1);
Откомпилируем и запустим:
me@myhost ~/instantclient_11_2/sdk/demo $ ./profile
Oracle Client Version: 11.2
11.2.0.3.0
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

PROFILEID  NAME                               DESCRIPTION                        BEH
========== ================================== ================================== ===
4160227384 RBS SuperChannel Circiut Profile   RBS SuperChannel Circiut Profile     0
Exception thrown for displayElements
Error number: 1455
ORA-01455: converting column overflows integer datatype

Done.
В разделе Determining the Oracle Client and Server Versions at Run Time написано следующее:
During run time, you can check both the client and server versions of the current Connection by using the getClientVersion(), getServerVersion(), and getServerVersionUString() methods. Это не совсем так. Есть нюанс. Да, метод getServerVersion() действительно определен в классе Connection:
virtual OCCI_STD_NAMESPACE::string getServerVersion() const =0;
а вот метод getClientVersion() определен на самом деле не в классе Connection, а в классе Environment в файле occiControl.h:
static void getClientVersion(int &majorVersion, int &minorVersion, 
                             int &updateNum, int &patchNum, int &portUpdateNum);


Вот почти вся премудрость. Для познания всей премудрости полезно прочитать C++ Call Interface Programmer's Guide...

Видите, Балаганов, что можно сделать из простой швейной машинки Зингера? Небольшое приспособление – и получилась прелестная колхозная сноповязалка.

(И.Ильф, Е.Петров "Золотой теленок")

пятница, 4 марта 2016 г.

Errata-логия смышленого указателя.

Намедни случился занятный случай. Попросила меня одна мамаша, сын которой учится на программиста, посоветовать учебник по C++.
Хорошенькое дело: книг по "плюсам" издано немало и которую из них посоветовать? Ну конечно же это старый добрый Герберт Шилдт и его знаменитый "Самоучитель С++" 3-е изд. 2003 года! Любой просветленный это скажет. Увы, не скажет, ибо старовата книга сия...
Из известных современных книг по "плюсам" больше всего лично мне понравилась "«Язык программирования C++. Лекции и упражнения» (описан C++11), Стивен Прата, 6-е издание, 1248 стр., ISBN 978-5-8459-1778-2, «ВИЛЬЯМС», 2012. Именно ее я и предложил в качестве учебника.
Материнская любовь к своему сыну-студенту настолько велика, что книга, объемом 1248 страниц, а именно столько страниц в книге Стивена Прата, показалась ей слишком тяжелой то ли для освоения C++, то ли по своим весовым характеристикам, но меня попросили предложить что-нибудь "полегче" и в частности высказать свое мнение о другой, в два раза более тонкой книге, а именно о книге «Освой самостоятельно C++ за 21 день» (описан стандарт C++11) , Сиддхартха Рао, 7-е издание, 688 стр., ISBN 978-5-8459-1825-3, «ВИЛЬЯМС», 2013.
Было острое желание сказать, что книга хорошая и тем самым быстренько отделаться от "клиента", который книги оценивает исключительно по их весовым качествам, но я решил все-таки фрагментарно посмотреть эту книгу и избрал для этого ЗАНЯТИЕ 26 "Понятие интеллектуальных указателей".
Приступил к чтению и сразу стало интересно, ибо "странности" нашлись совсем недалеко от начала главы.
Замечу, что в данном контексте стиль программирования меня не волнует. Стиль трогать не будем. Мне не случалось видеть, чтобы в спорах о стиле один программист в чем-то убедил другого. Исходя из этого, любые дискуссии на эту тему считаю бесполезной тратой времени. Приемлемый стиль должен раздражать всех читателей в равной мере. :)
Начало повествования было бодрым и незамедлительно стало ясно почему интеллектуальный указатель способен функционировать как обычный указатель, но потом начался ЛИСТИНГ 26.1. "Минимально необходимые компоненты класса интеллектуального указателя". Взгляд зацепился за очевидную Erratum в теле деструктора класса smart_pointer. Да вот этот листинг:
template <typename T> class smart_pointer {
private:
    T* m_pRawPointer;
public:
    smart_pointer (T* pData) : m_pRawPointer (pData) {}        // constructor
    ~smart_pointer () {delete pData;};                         // destructor
    smart_pointer (const smart_pointer & anotherSP);           // copy constructor
    smart_pointer& operator= (const smart_pointer& anotherSP); // copy assignment operator 
    T& operator* () const { return *(m_pRawPointer); }         // dereferencing operator
    T* operator-> () const { return m_pRawPointer; }           // member selection operator
};

// empty stub to enable compilation
int main() {
    return 0;
}
Неужели вот так сразу и ошибка? Не может быть! А давайте-ка спросим у компилятора:
$ g++ -Wall MinimalSmartPtr.cpp -o MinimalSmartPtr
MinimalSmartPtr.cpp: In destructor «smart_pointer::~smart_pointer()»:
MinimalSmartPtr.cpp:8:31: ошибка: нет декларации «pData» в этой области видимости
     ~smart_pointer () {delete pData;};                    // destructor
                               ^
Не понравилось ему. Поправим:
~smart_pointer () {delete /* pData; */ m_pRawPointer;}; // destructor
и опять спросим. Теперь все в порядке. Ничего не поделаешь. Всякое случается.
Дальше читаем. Вот подраздел "Анализ" и что видим? Цитирую:

Например, чтобы использовать интеллектуальный указатель на объект класса Tuna, вы создали бы его экземпляр так:
smart_pointer <Tuna> pSmartTuna (new Tuna); 
pSmartTuna->Swim ();
// Альтернатива:
(*pSmartDog).Swim ();
Конец цитаты.
А pSmartDog, который тоже плавать умеет, здесь при чем? Может разумнее было бы показать осмысленный фрагмент кода, расширяющий ЛИСТИНГ 26.1, например, так:
class Tuna {
public:
    void swim() { std::cout << "Tuna can swim." << std::endl; }
};

int main() {
    smart_pointer <Tuna> pSmartTuna (new Tuna);
    pSmartTuna->swim();
    (*pSmartTuna).swim();
    return 0;
}
Этот код и компилироваться будет, и сомнительных "указателей на умных псин" (*pSmartDog) здесь не будет, и при запуске программа выдаст понятный вывод:
$ ./MinimalSmartPtr.exe
Tuna can swim.
Tuna can swim.
Должен заметить, что у Стивена Прата все как-то доступнее и более предсказуемо описано, и нет нужды неискушенному читателю гадать о том, с чего это у автора книги тунец и собака плавают.
А чего это я тут брюзжать вздумал? Не брюзжание это, а грусть за набитые шишки у тех, кто будет C++ по такой, местами загадочной, книге изучать. И ведь книга не так уж и плоха. It’s nothing personal. Ей надо отдать должное, но мне почему-то не хотелось бы, чтобы мое чадо лишний раз по граблям при изучении языка топталось.

Дальше автор как-то сразу перешел к беглому обзору типов интеллектуальных указателей и в частности к глубокому копированию, введя в обсуждение базовый класс Fish. А зачем, хочется спросить, была вся эта суета около вышеприведенного класса smart_pointer?
Давайте эту незаконченную авторскую мысль попробуем все-таки закончить. Для этого добавим базовый класс и поглядим на получившуюся картину, но сначала все предельно упростим и на минуточку вспомним о необходимости определения виртуального деструктора в интерфейсе:
#include <iostream>

class Fish {
public:
    virtual void swim() = 0;    
};

class Tuna: public Fish {
public:
    void swim() { std::cout << "Tuna can swim." << std::endl; }
};

int main() {
    Fish *pTuna = new Tuna(); 
    pTuna->swim();
    (*pTuna).swim();

    // The behaviour of the next line is undefined. It probably 
    // calls Fish::~Fish, not Tuna::~Tuna        
    delete pTuna;
    return 0;
}

$ g++ -Wall tuna1.cpp -o tuna1
tuna1.cpp: В функции «int main()»:
tuna1.cpp:21:12: предупреждение: deleting object of abstract class type «Fish» which 
has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
     delete pTuna;
            ^
Классика предмета - "Why should I declare a virtual destructor for an abstract class in C++?", а о ней ни слова! Как на мой взгляд, напомнить о ней здесь было бы совсем даже нелишне. Теперь можно показать вариант лечения данной проблемы. Например, вот такой:
#include <iostream>

class Fish {
public:
    virtual ~Fish() {}; //You should always use a virtual destructor with interfaces.         
    virtual void swim() = 0;    
};

class Tuna: public Fish {
public:
    ~Tuna() { std::cout << "Tuna destructor." << std::endl; };
    void swim() { std::cout << "Tuna can swim." << std::endl; }
};

int main() {
    Fish *pTuna = new Tuna(); 
    pTuna->swim();
    (*pTuna).swim();

    delete pTuna;
    return 0;
}
Либо, чтобы быть уже совсем в фарватере обсуждаемой темы и забегая чуть вперед, - вот такой вариант:
#include <iostream>
#include <memory>

class Fish {
public:
    virtual ~Fish() {}; //You should always use a virtual destructor with interfaces. 
    virtual void swim() = 0; 
};

class Tuna: public Fish {
public:
    ~Tuna() { std::cout << "Tuna destructor." << std::endl; };
    void swim() { std::cout << "Tuna can swim." << std::endl; }
};

int main() {
    //Fish *pTuna = new Tuna(); 
    std::shared_ptr<Fish> pTuna(new Tuna());
    pTuna->swim();
    (*pTuna).swim();

    return 0;
}
Здесь нужно не забыть о флаге c++11:
$ g++ -std=c++11 -Wall tuna.cpp -o tuna
И чтобы вся эта суета вокруг smart_pointer была осмысленной, покажем полный пример с smart_pointer:
#include <iostream>
template <typename T> class smart_pointer {
private:
    T* m_pRawPointer;
public:
    smart_pointer (T* pData) : m_pRawPointer (pData) {}        // constructor
    ~smart_pointer () {delete m_pRawPointer;};                 // destructor
    smart_pointer (const smart_pointer & anotherSP);           // copy constructor
    smart_pointer& operator= (const smart_pointer& anotherSP); // copy assignment operator 
    T& operator* () const { return *(m_pRawPointer); }         // dereferencing operator
    T* operator-> () const { return m_pRawPointer; }           // member selection operator
};

class Fish {
public:
    virtual ~Fish() {}; //You should always use a virtual destructor with interfaces. 
    virtual void swim() = 0;   
};

class Tuna: public Fish {
public:
    ~Tuna() { std::cout << "Tuna destructor." << std::endl; };
    void swim() { std::cout << "Tuna can swim." << std::endl; }
};

int main() {
    smart_pointer <Fish> pTuna (new Tuna);         
    pTuna->swim();
    (*pTuna).swim();
    // delete pTuna;
    // Когда интеллектуальный указатель выходит из области видимости, 
    // он освобождает память, на которую указывает (при помощи деструктора).

    return 0;
}

$ g++ -Wall tuna1.cpp -o tuna1
$ ./tuna1.exe
Tuna can swim.
Tuna can swim.
Tuna destructor.
Вот теперь можно и о глубоком копировании рассказывать...
Как на мой взгляд, примеры кода лучше нескольких страниц поясняющего текста и в частности текста, который не отличается особой прозрачностью для изучающего язык. Неужели автору книги было так сложно сделать осмысленные примеры к книге? При печати книги эти примеры, будучи помещенными в прилагаемый архив, перерасход бумаги не создали бы, а читатель сэкономил бы немало драгоценного времени.

Все. Надоело. Будет настроение, почитаю дальше эту книгу. Есть такое смутное ощущение, что ERRATизма там еще хватает...

среда, 2 марта 2016 г.

To certify, or not to certify: that is the question.

...Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles,
And by opposing end them? To die, to sleep()...
Акт 3, Сцена 1. "Гамлет".
Шекспир, однако. "Кого мы только не играли в своих коллективах! Лучше не вспоминать. Не пора ли, друзья мои, нам замахнуться на Вильяма, понимаете, нашего Шекспира?"[1]. Хорошая идея - "замахнуться на Шекспира", но несколько несвоевременная. Сейчас я тут не совсем о Шекспире, вернее даже совсем не о Шекспире, хотя Шекспира здесь тоже хватает.
Занятную мысль нашел я в "Building Microservices, Designing Fine-Grained Systems" By Sam Newman.
Смысл речей Сэма заключается в том, что IT-отрасль еще молода и мы, похоже на то, забыли об этом... Поэтому мы постоянно оглядываемся на другие профессии, пытаясь объяснить то, чем мы на самом деле занимаемся. Мы не врачи и не инженеры, но мы также и не водопроводчики и не электрики. Мы находимся в эдакой золотой середине, поэтому обществу трудно понять нас, а нам трудно понять, к какой категории мы относимся.
Мы заимствуем понятия из других профессий. Мы называем себя инженерами или архитекторами. Но так ли это? Архитекторы и инженеры отличаются строгостью и дисциплиной, о которых мы можем только мечтать. Важность их роли в обществе ни у кого не вызывает сомнений. Разговаривал я как-то с одним приятелем за день до того, как он стал квалифицированным архитектором. «Завтра, - сказал он, - если я, сидя в баре, дам тебе какой-нибудь совет насчет того, как что-нибудь построить, и ошибусь, то меня привлекут к ответственности. Мне могут предъявить иск, поскольку с точки зрения закона теперь я - квалифицированный архитектор и должен отвечать за свои ошибки». (I could get sued, as in the eyes of the law I am now a qualified architect and I should be held responsible if I get it wrong.) Важность данной работы для общества определяет то, что ею должны заниматься квалифицированные специалисты. В Великобритании, например, прежде чем назваться архитектором, нужно проучиться семь лет. Эта работа опирается на фундамент тысячелетних знаний. А что у нас в IT? А ничего. В том числе и поэтому я (то есть Сэм) считаю разные IT-сертификации совершенно бесполезной затеей, поскольку о том, как выглядит совершенство в нашем деле, мы знаем крайне мало. (Which is also why I view most forms of IT certification as worthless, as we know so little about what good looks like.)
Вот так поворот сюжета! Между прочим, книга очень интересная и Сэм твердо знает предмет, о котором пишет, но сейчас не о микросервисах и не о гексагональной архитектуре (hexagonal architecture), и не о Domain Driven Design, хотя тоже любопытные темы. Сейчас о сертификации, о ценности и смысле которой высказался Сэм.
Это что же получается? Все эти сертификаты и прочая макулатура - типичная профанация и зарабатывание денег сертифицирующими конторами или в этом есть какой-то другой, потаенный смысл?
Ненадолго отвлечемся и оглянемся в прошлое, в то самое прошлое, когда были серп и молот, Моральный кодекс строителя коммунизма и многое другое, среди которого были Свидетельства о рационализаторских предложениях и Авторские свидетельства.
Помнит еще кто, как выглядело это самое Авторское свидетельство? А выглядело оно очень солидно: тут тебе и всякие тоненькие зеленые рисунки как на деньгах, и красная блямба для печати, и герб. Одним словом - правильная такая бумага, которая имела тогда какое-то, местами формальное, значение, особенно для тех, кто строил карьеру ученого. После того, как серп и молот уже канули в Лету, моя теща случайно найдя на даче среди всякого барахла красивые бумажки с гербами СССР, которыми стали авторские свидетельства, приняла их поначалу за облигации благодаря их внушительному виду. Проку с этих бумажек никакого: ну кому нужны авторские свидетельства на какие-то там "железки", которые использовались в других больших и страшных "железках", которыми, как могучим военным кулаком, когда-то Советский союз грозил "пентагоновским ястребам"[2]? И отправились эти бумаги по назначению - в топку. Казалось бы, на этом история заканчивается, но натыкаюсь я как-то на самсунговское объявление на LinkedIn, о том, что нужны им "Инженер-исследователь, инженер-разработчик, инженер-программист, научный сотрудник (Южная Корея) / Research (R&D) Engineer, Software Engineer / SW Developer, Programmer, Scientist". Самое интересное то, что в их "рыбе" curriculum vitae есть специальный пункт, в котором нужно указать всякие-разные патенты и т.д. Ого! Оказывается я печку топлю весьма ценными бумажками? Возможно, что какую-то ценность эти бумаги смогли бы обрести только в том случае, если бы я вдруг решил, например, в R&D на Самсунг податься, а так - это все прах и пепел развалившейся советской империи. Наверное наделало бы шороху CV, в котором обнаружились бы подробные описания всех тех "железок" и прочих изысканий.
Но оставим рефлексию и вернемся к теме. Я это все веду к тому, что, Сэм, написавший замечательную книгу о микросервисах, тем не менее не может знать обо всех причудливых странностях человеческой судьбы. Однозначно говорить о том, полезная ли затея IT-сертификация и стоит ли коллекционировать прочие "ценные бумаги", подтверждающие достижения и разные скиллзы, можно только исходя из поставленной каждым индивидуумом для себя цели и выбранного пути ее достижения. А посему, каждый решает для себя сам: что есть совершенство в области IT и нужны ему сертификаты или не нужны. Думайте!
IT, как утвержает Сэм, - дело молодое и тут пока еще не все устаканилось. Ну а с сертификацией в бизнесе как обстоят дела? Ответ на этот вопрос нашелся в книге "Сам себе МВА", которую написал Джош Кауфман. Я позволю себе привести из нее цитату:
...мне предложили должность заместителя начальника отдела бренд-менеджмента в подразделении, занимающемся бытовой химией, — на этот пост обычно приглашались обладатели дипломов MBA ведущих учебных заведений.
На какое-то время я тоже задумался о получении диплома MBA... , но затем решил, что нет смысла тратить огромные деньги на «корочку», нужную, по сути, только для того, чтобы получить должность, которую мне предложили и так; к тому же работы мне хватило бы и без кучи заданий, которые пришлось бы делать при очно-заочном обучении.
Раздумывая над тем, как же поступить, я вспомнил совет Энди Уолтера — первого из заместителей директора Procter & Gamble, перед которым мне пришлось отчитываться: «Если вы потратите столько же времени и сил, сколько потребовало бы от вас получение диплома МВА, на хорошую работу и закрепление навыков, результат будет таким же». (У Энди не было степени МВА, только диплом инженера-электрика. Теперь Уолтер топ-менеджер мирового уровня в IТ-сфере и курирует несколько крупнейших международных проектов компании Procter & Gamble.)
Пожалуй, что на этом пока можно остановиться. Есть над чем подумать, не так ли?

Примечания
1. Кинофильм "Берегись автомобиля". Киностудия «Мосфильм». 1966 год. Реж. Э.Рязанов.
2. "Пентагоновские ястребы" - распространенный газетный штамп времен холодной войны.

CMake Tutorial: Adding a Library

Есть такой полезный инструмент по имени cmake.
Как откровение эта мысль явно не прозвучала, да я на это особо и не надеялся. Речь вот о чем. Понаблюдав за "танцами с бубном", которые исполнял мой коллега, пытаясь сотворить сборку проекта, я как-то походя посоветовал ему этот инструмент. Я даже и не предполагал, что при изучении CMake Tutorial могут возникнуть недоразумения. Там вроде бы как все прозрачно, хотя кто знает. Ну уж коль я сам посоветовал, то мне и растолковывать возникшую непонятность.
С первым шагом 1 - A Basic Starting Point (Step1) никаких недоразумений не возникло. И добавление номера версии (Adding a Version Number and Configured Header File) получилось без проблем. Заминка возникла на шаге 2 - Adding a Library (Step 2). Вероятно, что проблема случилась из-за того, что ссылка на "the Tests/Tutorial directory of the CMake source code tree" оказалась на самом деле в никуда, то есть в 404...
Особого смысла в пересказе этого "тюториала" я не вижу. Покажу здесь только сборку примера, описанного в документе. Оригинальные имена каталогов и файлов сохранены.
Создал папку проекта Tutorial2. Структура содержимого имеет вид:
$ tree Tutorial2
Tutorial2
├── CMakeLists.txt
├── MathFunctions
│   ├── CMakeLists.txt
│   ├── MathFunctions.h
│   └── mysqrt.cxx
├── tutorial.cxx
└── TutorialConfig.h.in

1 directory, 6 files
Файл CMakeLists.txt:
cmake_minimum_required (VERSION 2.8.4)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )

# should we use our own math functions?
option (USE_MYMATH "Use tutorial provided math implementation" ON)

# add the MathFunctions library?
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})
Файл TutorialConfig.h.in:
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
Файл tutorial.cxx:
                                                                   
// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

int main (int argc, char *argv[]) {
  if (argc < 2) {
    fprintf(stdout,"%s Version %d.%d\n",
            argv[0], Tutorial_VERSION_MAJOR, Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
  }
  double inputValue = atof(argv[1]);
#ifdef USE_MYMATH
  double outputValue = mysqrt(inputValue);
#else
  double outputValue = sqrt(inputValue);
#endif
  fprintf(stdout,"The square root of %g is %g\n", inputValue, outputValue);
  return 0;
}
Файл MathFunctions/CMakeLists.txt:
add_library(MathFunctions mysqrt.cxx)
Файл MathFunctions/MathFunctions.h:
double mysqrt(double inputValue);
Файл MathFunctions/mysqrt.cxx:
#include <math.h>
double mysqrt(double inputValue) { return sqrt(inputValue); }
Дальше уже все просто:
me@myhost ~/devel/teach
$ mkdir Tutorial2/build

me@myhost ~/devel/teach
$ tree Tutorial2
Tutorial2
├── build
├── CMakeLists.txt
├── MathFunctions
│   ├── CMakeLists.txt
│   ├── MathFunctions.h
│   └── mysqrt.cxx
├── tutorial.cxx
└── TutorialConfig.h.in

2 directories, 6 files

me@myhost ~/devel/teach
$ cd Tutorial2/build/

me@myhost ~/devel/teach/Tutorial2/build
$ cmake ..
-- The C compiler identification is GNU 4.9.3
-- The CXX compiler identification is GNU 4.9.3
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++.exe
-- Check for working CXX compiler: /usr/bin/c++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/me/devel/teach/Tutorial2/build

me@myhost ~/devel/teach/Tutorial2/build
$ make
Scanning dependencies of target MathFunctions
[ 25%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cxx.o
[ 50%] Linking CXX static library libMathFunctions.a
[ 50%] Built target MathFunctions
Scanning dependencies of target Tutorial
[ 75%] Building CXX object CMakeFiles/Tutorial.dir/tutorial.cxx.o
[100%] Linking CXX executable Tutorial.exe
[100%] Built target Tutorial

me@myhost ~/devel/teach/Tutorial2/build
$ ./Tutorial.exe
./Tutorial Version 1.0
Usage: ./Tutorial number

me@myhost ~/devel/teach/Tutorial2/build
$ ./Tutorial.exe 2
The square root of 2 is 1.41421
Вот, собственно, и вся премудрость. Простор для дальнейшего творчества оставлен.
В CMake Tutorial есть описание и других интересных возможностей, но так как вопросов больше не поступало, то на этом позволю себе подвести черту под своим повествованием.

понедельник, 29 февраля 2016 г.

Паттерн "Стратегия"

Сегодня, копаясь в архивах, случайно нашел замечательную книгу Eric Freeman "Head First Design Patterns".
Ощущения чем-то сродни тем, которые испытываешь от найденной на дачном чердаке книги во время уборки в жаркий летний день. С неожиданной находки сметаешь пыль и усевшись на каком-то старом хламе, с удовольствием начинаешь ее листать.
Быстро пробежал глазами первый паттерн "Стратегия", еще раз восхитившись тем, как толково и доступно все написано. Почему-то зацепила фраза о том, что для понимания сути примеров нужно как минимум знать Java или хотя бы С#. Ой, да ладно. А почему не C++ или Python? Я имею право на такую постановку вопроса, так как сейчас работаю в проекте, написанном на Java. Добрую часть карьеры я был иногда слишком "религиозен" насчет операционных систем и языков программирования, но затем понял, что не обязан приносить клятву верности только избранной технологии или языку и считать их лучшими, отметая остальные. Именно поэтому вместо популярных ныне Java и С# я в качестве развлечения реализовал описанный пример на C++:
#include <iostream>

namespace hfstrategy {
    class FlyBehavior {
        public:
            virtual void fly() = 0;
    };

    class FlyWithWings: public FlyBehavior {
        public:
            void fly() { std::cout << "I'm flying!!" << std::endl; }
    };

    class FlyRocketPowered: public FlyBehavior  {
        public:
            void fly() { std::cout << "I'm flying with a rocket." << std::endl; }
    };

    class FlyNoWay: public FlyBehavior  {
        public:
            void fly() { std::cout << "I can't fly." << std::endl; }
    };

    class QuackBehavior {
        public:
            virtual void quack() = 0;
    };

    class FakeQuack: public QuackBehavior {
        public:
            void quack() { std::cout << "Qwak" << std::endl; }
    };

    class MuteQuack: public QuackBehavior {
        public:
            void quack() { std::cout << "<< Silence >>" << std::endl; }
    };

    class Squeak: public QuackBehavior {
        public:
            void quack() { std::cout << "Squeak" << std::endl; }
    };

    class Quack: public QuackBehavior {
        public:
            void quack() { std::cout << "Quack" << std::endl; }
    };

    class Duck {
        FlyBehavior *flyBehavior;
        QuackBehavior *quackBehavior;
        public:
            Duck() { }
            void setFlyBehavior (FlyBehavior *fb) { flyBehavior = fb; }
            void setQuackBehavior(QuackBehavior *qb) { quackBehavior = qb; }
            virtual void display() = 0;
            void performFly() { flyBehavior->fly(); }
            void performQuack() { quackBehavior->quack(); }
            void swim() { std::cout << "All ducks float, even decoys!" << std::endl; }
    };

    class RubberDuck: public Duck {
        public:
            RubberDuck() {
                FlyNoWay objf;
                Squeak objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a rubber duckie" << std::endl; }
     };

    class MallardDuck: public Duck {
        public:
            MallardDuck() {
                FlyWithWings objf;
                Quack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a real Mallard duck" << std::endl; }
     };

    class ModelDuck: public Duck {
        public:
            ModelDuck() {
                FlyNoWay objf;
                Quack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a model duck" << std::endl; }
     };

    class RedHeadDuck: public Duck {
        public:
            RedHeadDuck() {
                FlyWithWings objf;
                Quack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a real Red Headed duck" << std::endl; }
     };

    class DecoyDuck: public Duck {
        public:
            DecoyDuck() {
                FlyNoWay objf;
                MuteQuack objq;
                setFlyBehavior(&objf);
                setQuackBehavior(&objq);
            }
            void display() { std::cout << "I'm a duck Decoy" << std::endl; }
     };
}

int main() {
    using namespace hfstrategy;
    Duck *ptrd;
    RubberDuck rubberduck;
    MallardDuck mallardduck;
    ModelDuck modelduck;
    RedHeadDuck redheadduck;
    DecoyDuck decoyduck;

    ptrd = &decoyduck;
    ptrd->display();
    ptrd->performFly();
    ptrd->performQuack();
    ptrd->swim();
    return 0;
}


$ g++ -g -Wall -std=c++11 duck.cpp -o duck
$ ./duck.exe
I'm a duck Decoy
I can't fly.
<< Silence >>
All ducks float, even decoys!
Вот уж воистину точно сказано было: "Когда коту программисту делать нечего, он удовольствия ради код с Жабы на Цэ Два Креста перепихивать станет!" :)
И что с этим делать? Удалить это все и забыть книгу до следующей "уборки на чердаке"? Решил оставить. Кто знает, может это все кому-нибудь пригодится, а если вдруг пригодится, то не забудьте слова "Банды четырех":
Зависимость от реализации может повлечь за собой проблемы при попытке повторного использования подкласса. Если хотя бы один аспект унаследованной реализации непригоден для новой предметной области, то приходится переписывать родительский класс или заменять его чем-то более подходящим. Такая зависимость ограничивает гибкость и возможности повторного использования. С проблемой можно справиться, если наследовать только абстрактным классам, поскольку в них обычно совсем нет реализации или она минимальна.
Продолжение Паттерн "Стратегия": жаркое из питона.

понедельник, 15 февраля 2016 г.

Мягкий путь программиста

И снова о книгах.
«Я называю трансцендентальным всякое познание, занимающееся не столько предметами, сколько видами нашего познания предметов, поскольку это познание должно быть возможным a priori» (Кант И., Соч., т. 3, М., 1964, с. 121)
Продолжая и дальше почти "по-чапаевски" путать Канта с Шопенгауэром, начал я читать довольно свеженькую книгу с интригующим философским названием "Путь программиста", ну прям-таки "Дао дэ дзин" для софтмэйкеров, хотя в оригинале название звучит весьма прозаично "Soft Skills The software developer's life manual". Автор Сонмез Джон.
Прочитал первые 12 глав. Главы небольшие, книга читается легко, написана неплохо и не нагружает слишком уж безапелляционными высказываниями, хотя некоторые мысли своей очевидностью вызывают желание пролистать пару страниц.
Привычка читать книги и выделять маркером интересные и/или сомнительно-занятные мысли позволяет в последствии освежить основные положения книги, не читая ее повторно полностью. Кое-что, помеченное желтым маркером, я привожу ниже.
  • Вы можете наладить все остальные области жизни, но, если не сумеете преодолеть прокрастинацию, неорганизованность и банальную лень, вам будет очень трудно выйти даже на вторую передачу.
  • Ваше отношение к тому, что вы можете предложить в качестве бизнеса, значительно влияет на то, как вы видите свою карьеру. Компании часто пересматривают свои продукты и улучшают их. Вам следует поступать так же. Услуги, которые вы предоставляете в качестве разработчика ПО, могут изменить свою ценность, и ваша задача заключается не только в том, чтобы оценить их стоимость, но и в том, чтобы определить, чем они отличаются от предложений тысяч других разработчиков ПО.
  • Cфокусируйтесь на том, чтобы стать специалистом, предоставляющим специализированные услуги определенной части клиентов.
  • Если бы вам пришлось описать одним предложением определенную услугу, которую вы можете предоставить перспективному работодателю или покупателю, как бы звучало это предложение?
  • Предоставленные сами себе, мы следуем по проторенной дорожке. Выбрать свой путь гораздо сложнее. Вместо этого беремся за первую попавшуюся работу и остаемся на этом месте до тех пор, пока не появляется место получше или нас не увольняют.
  • По какой бы причине вы ни отказывались определять свои цели, сейчас пришло время сделать это. Не завтра, не на следующей неделе, а именно сейчас. Каждый шаг, сделанный без понимания направления, сделан впустую. Вам не следует идти по жизни, не определив цели своей карьеры.
  • ...награждение за хорошее поведение мотивирует гораздо лучше, чем наказание за плохое.
  • Если хотите вдохновлять, хвалите, вместо того чтобы критиковать.
  • Я считаю разработчика предпринимателем, если он развивает свой бизнес или продукт с использованием профессиональных навыков. В то время как служащий и независимый консультант меняют свое время на деньги, предприниматель меняет свое время не на моментальную выручку, а на шанс получить высокую оплату в будущем. …Я все еще пишу код, но обычно не для определенного клиента. Я пишу код либо своего продукта, который создаю, либо для обучающих материалов.
  • Множество разработчиков не имеют определенной специализации. Большинство определяют свою специализацию исключительно языком программирования, с которым они работают. Вы, скорее всего, слышали: «Я пишу на C#» или «Я разработчик на Java». Подобная специализация слишком широкая. Она никак не описывает, какую работу вы можете выполнять. Язык программирования не поясняет мне ничего о том, каким разработчиком вы являетесь и что можете делать. Это говорит только об одном инструменте, который вы можете использовать для решения задач. … Если вы специалист, у вас будет немного потенциальных работодателей и клиентов, но вы станете гораздо привлекательнее для них как профессионал. Пока вашу специализацию не выбрало большое количество программистов, будет гораздо проще найти работу или клиента, чем если бы вы просто назвали себя разработчиком.
  • Правило специализации гласит следующее: чем уже ваша специализация, тем меньше потенциальных рабочих мест и тем больше шанс, что вы получите одно из них.
  • Иметь в команде разработчика, который может выполнять любую работу, довольно удобно, но компании и клиенты редко ищут именно такого человека. Даже несмотря на то, что вы отлично знаете все виды технологий и 50 языков программирования, вам все равно следует выбрать специальность, даже если она будет меняться время от времени.
  • Вам следует учиться не только разработке ПО. Уделите время вопросам лидерства, менеджмента и бизнеса, если планируете в будущем занять управляющие позиции.
  • Вам все еще нужно знать, сколько нужно брать с клиентов, чтобы обеспечить себя, но вам придется достичь точки, когда сможете обосновать заданную (или даже более высокую) цену на рынке. Это нужно делать, основываясь не на ставке, а на том, сколько ваша работа стоит для клиентов. Вы можете рассматривать свою деятельность как товар или как услугу, которая повышает прибыльность ваших заказчиков. Если вы решаете рассматривать свое занятие как товар, то можете посоревноваться за работу с другими разработчиками (притязания которых часто ниже ваших). В этом случае покупатель выберет фрилансера с самой низкой ставкой. Но, если вы продвигаете свои услуги, основываясь на том, сколько сможете сэкономить клиенту или как способны расширить его бизнес, можно рассчитать свою ставку исходя из того, какую пользу принесут клиенту ваши услуги. Именно поэтому важно иметь специализацию.
  • Помните, что нужно рассказать о пользе, которую вы можете принести, а не о том, что вы можете сделать с технической точки зрения.
  • Многие разработчики, только недавно ставшие предпринимателями, допускают распространенную ошибку — создают продукт до того, как найдут целевую аудиторию. Хотя может показаться логичным начать с создания продукта, вам следует избегать этой оплошности.
  • Если вы хотите создать продукт, первым делом нужно определить целевую аудиторию, которой может понадобиться ваше решение. У вас может быть общее представление о том, какую проблему вы можете решить для этой аудитории, но в большинстве случаев потребуется провести исследования, чтобы найти распространенную проблему, которая еще не решена или решена, но некачественно
  • Вместо того чтобы пытаться найти аудиторию, вы можете ее создать.
Ну, пока на этом и все.

Продолжение следует.

пятница, 12 февраля 2016 г.

"Чапаев и Пустота" или как перепутать Канта с Шопенгауэром.

В очередной раз с удовольствием перечитал Виктора Пелевина. Наиболее понравившиеся фрагменты я сохранил и решил опубликовать. Я знаю о существовании Материала из Викицитатника. Однако мне ближе именно те моменты, на которые я сам обратил внимание в процессе чтения, и они вовсе необязательно пересекаются с Викицитатником. Именно ими я и решил поделиться здесь.
...если пытаешься убежать от других, то поневоле всю жизнь идешь по их зыбким путям. Уже хотя бы потому, что продолжаешь от них убегать. Для бегства нужно твердо знать не то, куда бежишь, а откуда. Поэтому необходимо постоянно иметь перед глазами свою тюрьму.
Мир, в котором мы живем - просто коллективная визуализация, делать которую нас обучают с рождения. Собственно говоря, это то единственное, что одно поколение передает другому. Когда достаточное количество людей видит эту степь, траву и летний вечер, у нас появляется возможность видеть все это вместе с ними. Но какие бы формы не были нам предписаны прошлым, на самом деле каждый из нас все равно видит в жизни только отражение своего собственного духа. И если вы обнаруживаете вокруг себя непроглядную темноту, то это значит только, что ваше собственное внутреннее пространство подобно ночи. Еще хорошо, что вы агностик. А то знаете, сколько в этой темноте шастало бы всяких богов и чертей.
У нас внутри - весь кайф в мире. Когда ты что-нибудь глотаешь или колешь, ты просто высвобождаешь какую-то его часть. В наркотике-то кайфа нет, это же просто порошок или вот грибочки... Это как ключик от сейфа. Понимаешь?
Лес вокруг дрожал ровными радужными огнями непонятной природы, а в небе над поляной вспыхивали удивительной красоты мозаики, не похожие ни на что из того, что встречает человек в своей изнурительной повседневности. Мир вокруг изменился - он сделался гораздо более осмысленным и одушевленным, словно бы стало наконец понятно, зачем на поляне растет трава, зачем дует ветер и горят звезды в небе.
-Ты, Коль, сам подумай - у нас же страна зоной отродясь была, зоной и будет. Поэтому и Бог такой, с мигалками. Кто тут в другого поверит?
- А может, все и не так на самом деле. Может, не потому Бог у нас вроде пахана с мигалками, что мы на зоне живем, а наоборот - потому на зоне живем, что Бога себе выбрали вроде кума с сиреной.
- В лес бежим, быстро! - повторил Володин и вскочил на ноги.
- Какой лес? Ведь никакого леса нет на самом деле!
- Ты, главное, беги, а лес образуется. Давай беги!
- Простите, не понял, - сказал я, - что, мое воспаленное сознание порождает кошмар или само сознание является порождением кошмара?
- Это одно и то же, - махнул рукой Чапаев. - Все эти построения нужны только для того, чтобы избавиться от них навсегда. Где бы ты ни оказался, живи по законам того мира, в который ты попал, и используй сами эти законы, чтобы освободиться от них.
- А, - улыбнулся Чапаев, - вот вы о чем. Знаете, Петр, когда приходится говорить с массой, совершенно не важно, понимаешь ли сам произносимые слова. Важно, чтобы их понимали другие. Нужно просто отразить ожидания толпы. Некоторые достигают этого, изучая язык, на котором говорит масса, а я предпочитаю действовать напрямую.
...В терминологии Чапаева это означало изучить язык, на котором говорит масса. А сам он, как я понял, даже не утруждал себя знанием слов, которые произносил. Было, правда, неясно, как он этого достигает. Может быть, впадая в подобие транса, он улавливал эманации чужого ожидания и каким-то образом сплетал из них понятный толпе узор.
Мне не понравился их комиссар, - сказал я, - этот Фурманов. В будущем мы можем не сработаться.
- Не забивайте себе голову тем, что не имеет отношения к настоящему, - сказал Чапаев. - В будущее, о котором вы говорите, надо еще суметь попасть. Быть может, вы попадете в такое будущее, где никакого Фурманова не будет. А может быть, вы попадете в такое будущее, где не будет вас.
- ... человек чем-то похож на этот поезд. Он точно так же обречен вечно тащить за собой из прошлого цепь темных, страшных, неизвестно от кого доставшихся в наследство вагонов. А бессмысленный грохот этой случайной сцепки надежд, мнений и страхов он называет своей жизнью. И нет никакого способа избегнуть этой судьбы.
- Ну отчего, - сказал Чапаев. - Способ есть.
- И вы его знаете? - спросил я.
- Конечно, - сказал Чапаев.
Поразительно, сколько нового сразу же открывается человеку, стоит только на секунду опустошить заполненное окаменелым хламом сознание! Неясно даже, откуда приходит большая часть звуков, которые мы слышим. Что же тогда говорить обо всем остальном, какой смысл пытаться найти объяснения нашей судьбе и нашим поступкам, основываясь на том немногом, что, как нам кажется, мы знаем!
- Я, Василий Иванович, думаю о том, что любовь прекрасной женщины - это на самом деле всегда снисхождение. Потому что быть достойным такой любви просто нельзя.
...
- Серьезно? - спросил Чапаев. - Ну ладно. Тогда гляди - снисхождение всегда бывает от чего-то одного к чему-то другому. Вот как в этот овражек. От чего к чему это твое снисхождение сходит?
Я задумался. Было понятно, куда он клонит. Скажи я, что говорю о снисхождении красоты к безобразному и страдающему, он сразу задал бы мне вопрос о том, осознает ли себя красота и может ли она оставаться красотой, осознав себя в этом качестве. На этот вопрос, доводивший меня почти до безумия долгими петербургскими ночами, ответа я не знал. А если бы в виду имелась красота, не осознающая себя, то о каком снисхождении могла идти речь? Чапаев был определенно не прост.
Чапаев поглядел вверх и покачнулся.
- Красота? - переспросил он задумчиво. - А что такое красота?
- Ну как, - сказал я. - Как что. Красота - это совершеннейшая объективация воли на высшей ступени ее познаваемости.
Чапаев еще несколько секунд глядел в небо, а потом перевел взгляд на большую лужу прямо у наших ног и выплюнул в нее окурок. Во вселенной, отраженной в ровной поверхности воды, произошла настоящая катастрофа: все созвездия содрогнулись и на миг превратились в размытое мерцание.
- Что меня всегда поражало, - сказал он, - так это звездное небо под ногами и Иммануил Кант внутри нас.
- Я, Василий Иванович, совершенно не понимаю, как это человеку, который путает Канта с Шопенгауэром, доверили командовать дивизией.
- Все, что мы видим, находится в нашем сознании, Петька. Поэтому сказать, что наше сознание находится где-то, нельзя. Мы находимся нигде просто потому, что нет такого места, про которое можно было бы сказать, то мы в нем находимся. Вот поэтому мы нигде. Вспомнил?
- В древние времена, - сказал Кавабата, - в нашей стране чиновников назначали на важные посты после экзаменов, на которых они писали сочинения о прекрасном. И это был очень мудрый принцип - ведь если человек понимает в том, что неизмеримо выше всех этих бюрократических манипуляций, то уж с ними-то он без сомнения справится.
- Это тоже один из принципов нашей фирмы. Мы всегда стараемся проникнуть глубоко в душу того народа, с которым ведем дела. Дело здесь не в том, что мы хотим извлечь благодаря этому какую-то дополнительную прибыль, поняв... Как это по-русски? Ментальность, да?
Сердюк кивнул.
- Нет, - продолжал Кавабата. - Дело здесь скорее в желании возвысить до искусства даже самую далекую от него деятельность. Понимаете ли, если вы продаете партию пулеметов, так сказать, в пустоту, из которой вам на счет поступают неизвестно как заработанные деньги, то вы мало чем отличаетесь от кассового аппарата. Но если вы продаете ту же партию пулеметов людям, про которых вам известно, что каждый раз, когда они убивают других, они должны каяться перед тремя ипостасями создателя этого мира, то простой акт продажи возвышается до искусства и приобретает совсем другое качество. Не для них, конечно, - для вас. Вы в гармонии, вы в единстве со вселенной, в которой вы действуете, и ваша подпись под контрактом приобретает такой же экзистенциальный статус... Я правильно говорю это по-русски?
- А вот скажите, - заговорил он, - чего хочет человек, вернувшийся домой из опасного путешествия, после того как утолит жажду и голод?
- Не знаю, - сказал Сердюк. - У нас обычно телевизор включают.
- Не-е-е, - сказал Кавабата. - Мы в Японии производим лучшие телевизоры в мире, но это не мешает нам осознавать, что телевизор - это просто маленькое прозрачное окошко в трубе духовного мусоропровода. Я не имел в виду тех несчастных, которые всю жизнь загипнотизированно смотрят на бесконечный поток помоев, ощущая себя живыми только тогда, когда узнают банку от знакомых консервов.
I’m not a transcendentalist monk sitting on the floor meditating while eating Peyote buttons, trying to help you ascend to a higher state of consciousness, но мне очень нравится это замечательное произведение. В который раз его перечитываю, а свежесть мыслей и восприятия остаются прежними.
Еще в школьные годы довелось прочитать другого Чапаева, который без Пустоты... Это был Чапаев Д.Фурманова. Почему-то из всего произведения запомнилась только одна специфическая особенность: Фурманов зачем-то в тексте все время "штокал", и это ужасно раздражало.
- Научатся, браток, научатся... На фронт приедут - там живо сенькину мать куснут...
- А што думал - на фронте тебе не в лукошке кататься...
И все заерзали, засмеялись, шеями потянулись вперед.
- Вон Терентия не узнаешь, - в заварке-то мазаный был, как фитиль, а тут поди тебе... Козырь-мозырь...
- Фертом ходит, што говорить... Сабля-то - словно генеральская, ишь таскается.
Два совершенно разных Чапаева. Што и говорить, дуализЬм эдакий, понимаешь.

Думал над Гурджиевскими словами "Человек не имеет индивидуальности; у него нет единого большого Я. Человек расщеплен на множество мелких "я"...
Интересная получается картина, если эти слова попробовать анализировать с точки зрения официальной медицины с целью постановки диагноза, который по сути своей с позиций теории познания был бы субъективным отражением объективно существующей истины. Предприняв попытку познания этой самой объективной истины, можно прийти к интересным выводам. Понятие "диагноз", употребляемое в данном контексте, требует уточнения. Здесь речь идет о «диагнозе болезни», который медики называют еще «нозологическим диагнозом», а не о «диагнозе больного», то есть «индивидуальном диагнозе» или «патогенетическом диагнозе» (diagnosis aegroti).
Похоже на то, что Гурджиев описывает явную диссоциативную патологию личностной идентичности, вследствие которой личность индивида разделяется, что формирует впечатление, будто бы в одном человеке, а точнее человеческом субъекте, уживаются две и более личностей, то самое "множество мелких "я", имеющих право "подписывать чеки и векселя". Это множество мелких "я", по всей видимости, есть не что иное как эго-состояния.
"И каждое отдельное малое "я" может называть себя именем целого, действовать во имя целого, соглашаться или не соглашаться, давать обещания, принимать решения, с которыми придется иметь дело другому "я" или всему целому." Налицо "переключение", вследствие которого в индивиде одна личность ("мелкое я" по Гурджиеву) становится на замену другой. Находясь в различных эго-состояниях, индивид реагирует на одинаковые повседневные ситуации по-разному вследствие того, что каждое его эго имет свой собственный набор паттернов восприятия окружающего мира. Малое "я", захватившее в данный момент рычаги управления, после очередного "переключения" может и не помнить того, что происходило в периоды правления другого малого "я", и "может в какой-то момент; что-то пообещать уже не себе, а кому-то другому, просто из тщеславия или для развлечения. Затем это "я" исчезает; но человек, т.е. сочетание других "я", совершенно не ответственных за это обещание, вынужден расплачиваться за него в течение всей своей жизни." Здесь появляются довольно веские основания для констатации факта болезни раздвоения личности. Следовательно, причину существования гурджиевских "малых "я" можно рассматривать под углом зрения причин синдрома раздвоения личности.
Запуск того или иного "малого я", то есть переход индивидуума в одно из возможных "малых я" является функцией двух переменных - внутреннего состояния и входного воздействия (пускового триггера). Роль таких триггеров могут играть, например, травмирующие воздействия - стрессы. В любом случае переход от одного малого "я" к другому выглядит как защитная реакция психики, основывающаяся на полученном ранее травматичесом опыте.
Характерным признаком раздвоения идентичности является частичная невозможность доступа к информации, приобретенной одной личностью, во время правления другой. В случае гурджиевских "малых я" все личности имеют доступ практически ко всей информации, которая в этом случае является коллективным приобретением, что при значительном количестве, а главное - уровне самоуправства каждого "малого я" может основательно осложнить жизнь индивидуума, того "большого Я", которого может и не оказаться.
И если ты долго смотришь в Пустоту, то Чапаев будет вглядываться в тебя. Как это будет, ежели обратно на немецкий перепихнуть? Э-э[1]... Und wenn du lange in eine Leere blickst, blickt Tschapajew auch in dich hinein. Брр!.. Ницшеанство какое-то... :)

Примечания
1. "Сражающемуся с чудовищами следует позаботиться о том, чтобы самому не превратиться в чудовище. И если ты долго смотришь в бездну, то бездна тоже смотрит в тебя." (Фридрих Ницше)

пятница, 5 февраля 2016 г.

Как кратко и доступно объяснить суть динамического полиморфизма?

Недавно на посиделках в кафе в ходе обсуждения какой-то технической темы промелькнули слова "динамический полиморфизм", которые вызвали неожиданный интерес у одного из собеседников, ранее писавшего на скриптовом языке и подозревающего о том, что где-то есть какие-то загадочные указатели... Возник вопрос, формулировка которого вынесена в заголовок этого поста.
Вопрос далеко не оригинальный и ответ на него давно известен. И тем не менее, ну как быстро, кратко и доступно объяснить суть динамического полиморфизма, не залезая в дебри и не загружая вопрошающего кучей подробностей? Отмахнуться от собеседника, предложив спросить у Google, или все-таки попробовать объяснить? Объяснение было мною дано и на какой-то старой квитанции написан код, а потом возникла идея: сохранить это объяснение здесь, в когда-то давно начатом и потом забытом блоге "Записи на манжетах." Откуда взялось такое название? Все просто. И.Ильф и Е.Петров "Золотой теленок":

- Вот как! - сказал Остап. - А себя вы считаете, очевидно, врачом-общественником? Джентльменом? Тогда вот что: если вам, как истому джентльмену, взбредет на мысль делать записи на манжетах, вам придется - писать мелом.
- Почему? - раздраженно спросил новый пассажир.
- Потому что они у вас совершенно черные. Не от грязи ли?
- Вы жалкий, ничтожный человек! - быстро заявил Паниковский.
- И это вы говорите мне, своему спасителю? - кротко спросил Остап, - Адам Казимирович, остановите на минутку вашу машину. Благодарю вас. Шура, голубчик, восстановите, пожалуйста, статус-кво.


Итак, существует термин "раннее связывание", касающийся настройки вызовов обычных функций, при компиляции которых известна вся необходимая для их вызова адресная информация. Настройка вызовов может происходить не только при компиляции, но и в процессе выполнения программы - это, так называемое, позднее связывание. Вызов функции позднего связывания - это вызов, при котором адрес вызываемой функции до запуска программы неизвестен и определяется в процессе выполнения программы.
В C++ функцией позднего связывания является виртуальная функция, которая может вызываться так же, как и любая другая функция-член.
Динамический полиморфизм поддерживается за счет использования вызова виртуальной функции через указатель базового класса. Указатель, объявленный в качестве указателя на базовый класс, также может использоваться, как указатель на любой класс, производный от этого базового. В этом случае в процессе работы программа обязана определить, на какой тип объекта ссылается этот указатель, а затем выбрать, какую версию подменяемой функции выполнить. Определение конкретной версии виртуальной функции имеет место не в процессе компиляции, а уже в процессе выполнения программы.
Есть некий базовый класс, содержащий виртуальную функцию, и различные производные от него классы. Если указатель базового класса ссылается на разные объекты этих производных классов, то выполняются различные версии виртуальной функции. Этот процесс по сути своей и является реализацией принципа динамического полиморфизма.

Пример.
Есть полиморфный базовый класс Base с чистой виртуальной функцией func(). Этот класс наследуется двумя производными классами Derived1 и Derived2, по-своему переопределяющими виртуальную функцию базового класса. Выбор конкретной версии функции func() происходит непосредственно во время работы программы. При компиляции этот выбор сделать невозможно, поскольку он основан на значениях, определяемых вызовом функции rand(), и которые можно получить только во время работы программы. Если в ходе работы генерируется нечетное число, то используется объект obj1. В противном случае - объект obj2.
#include <iostream>
#include <cstdlib>
using namespace std;

class Base {
public:
    int i;
    Base(int x) { i = x; }
    virtual void func() = 0;
};

class Derived1: public Base {
public:
    Derived1(int x) : Base(x) { }
    void func() { cout << "Call func() in Derived1: " << i+i << endl; }
};

class Derived2: public Base {
public:
    Derived2(int x) : Base(x) { }
    void func() { cout << "Call func() in Derived2: " << i*i << endl; }
};

int main() {
    Base *p; 
    Derived1 obj1(5); 
    Derived2 obj2(5); 

    for (int i=0; i<5; i++) {
        if (rand() % 2) {
            p = &obj1;
        } else {
            p = &obj2;
        }
        p->func();
    }
    return 0;
}
Примерно так все и выглядело. Разговор потом развивался дальше в сторону множественного наследования, но к этому моменту тот, кто задал изначальный вопрос, уже ушел. Хочется верить, что он все или почти все понял. Именно по его просьбе и для него это все было рассказано и потом записано. :)
Если на очередных посиделках опять будут подобные вопросы, то ответы на них я тоже постараюсь опубликовать, дабы поддержать эту дебютную идею.

Спустя месяц после опубликования поста, эту тему опять вспомнили. Решено было прямо здесь ее продолжить. Была высказана просьба - "намекнуть на типичные грабли, которые могут случаться" в смысле "what can possibly go wrong".
Просьбу удовлетворяю и намекаю:
#include <iostream>
class Base {
public:
   virtual void func(short) {std::cout << "Base::func" << std::endl;}
   virtual void func1(short) const {std::cout << "Base::func1" << std::endl;}
};

class Derived: public Base {
public:
   // Это перегруженные методы!
   virtual void func(int) {std::cout << "Derived::func" << std::endl;}
   virtual void func1(short) {std::cout << "Derived::func1" << std::endl;}
};

int main() {
    Base base;
    Derived obj; 
    Base *p;        
        
    p = &base;
    p->func(5);
    p->func1(5);
        
    p = &obj;
    p->func(5);
    p->func1(5);

    return 0;
}

$ g++ -Wall dinp.cpp -o dinp
$ ./dinp.exe
Base::func
Base::func1
Base::func
Base::func1
И чтобы блюдо получилось, чуточку приправим все щепоткой Страуструпа, пример из которого я слегка поправил для большей наглядности:
#include <iostream>

class Base {
public:
    virtual void func() const { std::cout << "Base::func() " << std::ends; }
    void g() const { std::cout << "Base::g()" << std::endl; }
};

class Derived: public Base {
public:
    void func() const { std::cout << "Derived::func() " << std::ends; } // Не замещает Base::func()
    void g() const { std::cout << "Derived::g()" << std::endl; }
};

class Derived2: public Derived {
public:
    void func() { std::cout << "Derived2::func() " << std::ends; }  // Не замещает Derived::func()
    void g() const { std::cout << "Derived2::g()" << std::endl; }
};

void call(const Base& b) {
    b.func();
    b.g();
}

int main() {
    Base b;
    Derived d;
    Derived2 d2;

    call(b);   // Base::func() Base::g()
    call(d);   // Derived::func() Base::g()
    call(d2);  // Derived::func() Base::g()

    b.func();  // Base::func().
    b.g();     // Base::g()

    d.func();  // Derived::func()
    d.g();     // Derived::g()

    d2.func(); // Derived2::func()
    d2.g();    // Derived2::g()

    return 0;
}
Тут есть еще о чем поговорить, плавно съехав в C++ версии 11, но это будет явный перебор. На этом закругляемся. (It took me quite some time to become aware of an amazing analogy that exists between the culinary art and the art of computer programming.)