четверг, 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 есть описание и других интересных возможностей, но так как вопросов больше не поступало, то на этом позволю себе подвести черту под своим повествованием.