суббота, 10 декабря 2011 г.

Некоторые базовые понятия OpenGL


Область (объем) отсечения определяется минимальным и максимальным значениями пространственных (логических декартовых) координат, которые видно в окне.
Поле просмотра - это область внутри клиента окна, которое используется для рисования области отсечения. Измеряется в пикселах или реальных экранных координатах. Поле просмотра обычно эквивалентно области окна, но не всегда. С помощью поля просмотра можно сжимать или растягивать изображения внутри окна, а также отоьражать только часть области отсечения (в этом случае поле просмотра задается больше чем область клиента окна).
Есть два типа отображения трехмерной области отсечения на поле просмотра: это ортографическая и перспективная проекции.
glViewport - используется для задания поля просмотра в окне.
glOrtho - устанавливает границы объема отсечения.
Между заданием вершин и появлением их на экране происходит три типа преобразований:
1)наблюдения;
2)модели;
3)проецирования;
Терминология преобразований OpenGL
1)преобразование наблюдения - задает положение камеры;
2)преобразование модели - перемещает объекты по сцене;
3)преобразование наблюдения модели - описывает "дуализм" преобразований наблюдения и ммодели;
4)преобразование проецирования - задает наблюдаемый объем;
5)преобразование поля просмотра - псевдопреобразование, масштабирующее конечный результат согласно размерам окна.
Координаты наблюдения отсчитываются от точки, в которой расположен глаз наблюдателя (эти координаты можно рассматривать как абсолютные экранные координаты). Все преобразования описываются с точки зрения их эффекта в системе координат наблюдения. При отсутствии преобразований система координат наблюдения такова, что камера находится в начале координат и смотрит по направлению отрицательных z. Верх камеры ориентирован в этом случае в сторону положительных у.
Проекция модели (modelview) означает, что это преобразование можно считать либо преобразованием модели либо преобразованием проецирования. Разницы между ними для конечного результата нет.
Тоже касается преобразования наблюдения модели.
Исходное преобразование дает точку отсчета для других преобразований.
Конвейер преобразований вершины
1)исходные данные по вершине:
2)матрица наблюдения модели;
3)преобразованные координаты наблюдения;
4)матрица проекции;
5)координаты отсечения;
6)перспективное деление и преобразование поля просмотра;
7)координаты окна;
glFrustum - трехмерный аналог gluOrtho2D. Создает матрицу перспективной проекции и умножает ее на текущую. Наблюдатель расположен в (0,0,0) и смотрит в отрицательном направлении z.
gluLookAt - создает видовую матрицу. Определяет преобразование наблюдения на основе положения наблюдателя, центра сцены и вектора, указывающего "вверх" от наблюдателя.
gluPerspective - создает матрицу наблюдения перспективной проекции, описывающую наблюдаемый объем (усеченная пирамида) в глобальных координатах. Аспект должен соответствовать характеристическому отношению поля просмотра (задается glViewPort)

пятница, 9 декабря 2011 г.

Кватернион и матрица вращения OpenGL

Кваренион - это такая четырехкомпонентная сущность, с помощью которой можно задавать вращения в трехмерном пространстве. Их преимущество перед матрицами, соответствующим этим поворотам, состоит в том, что для них, кватернионов, нет так называемой "шарнирной блокировки" и что вычисления кватернионной математики менее ресурсоемки, чем для соответствующих матричных операций. Но в OpenGL пока еще манипулируют поворотами в пространстве с помощью матриц. Посмотрим, как соотносятся кватернион и соответствующая ему матрица, задающая поворот.
Кватернион представляется в виде черырех чисел x, y, z, w:

где:
угол альфа выражается в радианах, а е - это единичный вектор, вокруг которого совершается вращение.
Вектор v переводится вращением в вектор v' с помощью кватерниона так:

Матрица поворота выражается через компоненты кватерниона следующим способом:

Не забываем, что в OpenGL матрицы разворачиваются по столбцам, а не построкам. Таким образом, если матрицу обозначаем через matrixOfRotation, то, используя синтаксис языка си мы ее элементы запишем следующим образом:


matrixOfRotation[0] = 1.0f - 2.0f*( y*y + z*z );
matrixOfRotation[1] = 2.0f * ( x*y + z*w);
matrixOfRotation[2] = 2.0f * ( x*z - y*w);
matrixOfRotation[3] = 0.0f;
matrixOfRotation[4] = 2.0f * ( x*y - z*w );
matrixOfRotation[5] = 1.0f - 2.0f*( x*x + z*z );
matrixOfRotation[6] = 2.0f * ( z*y + x*w );
matrixOfRotation[7] = 0.0f;
matrixOfRotation[8] = 2.0f * ( x*z + y*w );
matrixOfRotation[9] = 2.0f * ( y*z - x*w );
matrixOfRotation[10] = 1.0f - 2.0f*( x*x + y*y );
matrixOfRotation[11] = 0.0f;
matrixOfRotation[12] = 0;
matrixOfRotation[13] = 0;
matrixOfRotation[14] = 0;
matrixOfRotation[15] = 1.0f;


четверг, 27 октября 2011 г.

Создание видеоролика из галереи jpg-файлов и обратная операция


Создать галерею изображений из видеофайла можно утилитой mplayer:
$mplayer -vo jpeg -vf framestep=5 yourfile.mpg

вместо -vf framestep=5 можно использовать что-то вроде этого: -sstep 30 -frames 10, но эта конструкция не всегда работает. Чем меньше параметр у framestep, тем больше будет выведено картинок.
Обратная операция, а именно создание видеофайла из галереи файлов jpg, осуществима с помощью утилиты mencoder:
$mencoder "mf://*.jpg" -mf fps=0.5 -o yourfile.avi -ovc lavc -lavcopts vcodec=msmpeg4:vbitrate=1800

Чем меньше параметр fps, тем дольше в созданном видео будет показываться отдельная картинка.
Все команды запускались из командной строки в Debian Squeeze.
Добавить звуковое сопровождение в созданный ролик:
$mencoder -ovc copy -audiofile soundtrack.mp3 -oac copy videoWithoutSound.avi -o videoWithSound.avi

Удалить:
$mencoder -ovc copy -nosound videoWithSound.avi -o videoWithoutSound.avi

воскресенье, 23 октября 2011 г.

Создание простейшего приложения с графическим интерфейсом, используя Glade и gtkmm

Создание графического интерфейса с помощью Glade и gtk+ было рассмотрено в этом же блоге ранее. Вот ссылка. В данном посте заменим gtk+ на gtkmm.
Логика выполнения программы остается той же:
1)создается "xml-объект" (это чисто мой термин, возможно неудачный) из файла, созданного в интуитивно понятном графическом редакторе Glade;
2)из этого объекта "вытягиваются" имена элементов графического интерфейса, с которыми мы будем оперировать в коде;
3)из него же вытягиваем имена обработчиков сигналов для этих элементов и привязываем их каждого к своему элементу;
4)пишем код, реализующий эти обработчики;
Теперь все это в картинках:
Создаем главное окно приложения:

Придумываем ему какой-нибудь заголовок:

Создаем место для меню (в нашем случае оно и не надо для наших целей, но просто для красоты):

Добавляем это самое меню (красный кружок слева) и удаляем одно из мест для кнопок (красный кружок справа), так как у нас будет одна только кнопка, для выхода:

Добавляем в оставшееся место для кнопок кнопку из стандартного набора (это будет quit):

И добавляем обработчик сигнала (в нашем случае щелчок мыши) к созданной кнопке на вкладке Signals:

Сохраняем файл. Обратите внимание на File format в левом нижнем углу:

Теперь создаем файл hello.cpp:

Выделенные части должны иметь свои соответствия в предыдущих уже описанных нами действиях.
Компилируем:
$g++ hello.cpp `pkg-config gtkmm-2.4 libglademm-2.4 --cflags --libs`
и запускаем на выполнение:
$a.out
Должно получиться что-то вроде этого:

Все описанные действия выполнялись на Debian Squeeze.
Для облегчения изучения вот код не картинкой, а текстом:
#include <libglademm/xml.h>
#include <gtkmm.h> 
#include <iostream>
Gtk::Dialog* pDialog = 0;

void
button1_clicked_cb(){
 if(pDialog)
 pDialog->hide(); //hide() will cause main::run() to end.
}

int
main (int argc, char **argv){
 Gtk::Main kit(argc, argv);
 //Load the Glade file and instiate its widgets:
 Glib::RefPtr <Gnome::Glade::Xml>refXml;
 try{
      refXml = Gnome::Glade::Xml::create("hello.glade");
 }
 catch(const Gnome::Glade::XmlError& ex){
      std::cerr << ex.what() << std::endl;
      return 1;
 }
 //Get the Glade-instantiated Dialog:
 refXml->get_widget("dialog1", pDialog);
 if(pDialog){
      //Get the Glade-instantiated Button, and connect a signal handler:
      Gtk::Button* pButton = 0;
      refXml->get_widget("button1", pButton);
      if(pButton){
          pButton->signal_clicked().connect( sigc::ptr_fun(button1_clicked_cb) );
      }
      kit.run(*pDialog);
 }
 return 0;
}

Красивых интерфейсов вам.

четверг, 20 октября 2011 г.

Простейшие примеры использования CUDA

Что такое CUDA? Смотрите в википедии.
Рассмотрим коротко простейшие примеры использования этой интересной технологии. Этот пост является конспектом презентации Introduction to CUDA C автора Jason Sanders на конференции NVIDIA GTC 2010 - GPU Technology Conference. (Pre-Conference Tutorials, GTC 2010 - ID 2131).
Итак. Самая базовая терминология:
Host - CPU и его память (host memory);
Device - GPU и его память (device memory);
Начнем с “Hello, World!”. А куда ж без него?
Код делится на тот, что выполняется на CPU и тот что выполняется на GPU. Следующий самый просто пример, это программа, которая выводит приветствие миру и вызывает функцию из GPU, которая ничего не делает:

__global__ void kernel( void ) {
}

int main( void ) {
kernel<<< 1, 1 >>>();
printf( "Hello, World!\n" );
return 0;
}

Ключевое слово __global__ в объявлении и определении функции обозначает, что функция выполняется на графической карте. И что эта функция вызывается из CPU. Неизменным аттрибутом такой функции являются скобки <<< >>> при вызове этой функции. Аргументы внутри этих скобок обсудим ниже. Компилятор nvcc разделяет исходный код на части, которые выполняются отдельно на хосте и девайсе. Код, сгенерированный для таких функций как kernel из данного примера, выполняется на девайсе. Кстати, как я понял, device - это не обязательно GPU.
Рассмотрим более сложный пример. CPU передает некие величины в GPU для сложения их там.
__global__ void add( int *a, int *b, int *c ) {
*c = *a + *b;
}

int main( void ) {
int a, b, c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии of a, b, c
int size = sizeof( int );
//выделяем память для device копий для a, b, c
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
a = 2;
b = 7;
// копируем ввод на device
cudaMemcpy( dev_a, &a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, &b, size, cudaMemcpyHostToDevice );
// запускаем add() kernel на GPU, передавая параметры
add<<< 1, 1 >>>( dev_a, dev_b, dev_c );
// copy device result back to host copy of c
cudaMemcpy( &c, dev_c, size, cudaMemcpyDeviceToHost );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
return 0;
}

Базовый CUDA API для работы с памятью GPU (и девайса вообще):
cudaMalloc();
cudaFree();
cudaMemcpy();
Их Си аналоги: malloc(), free(), memcpy().
__global__ void add( int *a, int *b, int *c ) {
c[blockIdx.x] = a[blockIdx.x] + b[blockIdx.x];
}

#define N 512
int main( void ) {
int *a, *b, *c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии a, b, c
int size = N * sizeof( int );
//выделяем память для device копий a, b, c
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
a = (int*)malloc( size );
b = (int*)malloc( size );
c = (int*)malloc( size );
random_ints( a, N );
random_ints( b, N );
// копируем ввод на device
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
// launch add() kernel with N parallel blocks
add<<< N, 1 >>>( dev_a, dev_b, dev_c );
// копируем результат работы device обратно на host - копию c
cudaMemcpy( c, dev_c, size, cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
return 0;
}

Главное отличие этого кода от предыдущих это аргумент N в вызове функции add<<< N, 1 >>>. Он обозначает число так называемых блоков, на которые разбивается код на девайсе при распараллеливании. Обращаться к отдельному блоку можно через индекс массива блоков blockIdx.x (это встроенная переменная). Блоки, в свою очередь могут разбиваться на нити (threads)
Перепишем наш код для использования нитей, а не блоков (будем использовать не N блоков по одной нити, а один блок с N нитями):
__global__ void add( int *a, int *b, int *c ) {
c[ threadIdx.x ] = a[ threadIdx.x ] + b[ threadIdx.x ];
// blockIdx.x blockIdx.x blockIdx.x
}

#define N 512
int main( void ) {
int *a, *b, *c; //host копии a, b, c
int *dev_a, *dev_b, *dev_c; //device копии of a, b, c
int size = N * sizeof( int );
//выделяем память для копий a, b, c
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
a = (int*)malloc( size );
b = (int*)malloc( size );
c = (int*)malloc( size );
random_ints( a, N );
random_ints( b, N );
// копируем ввод device
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
// запускаем на выполнение add() kernel с N нитями в блоке
add<<< 1, N >>>( dev_a, dev_b, dev_c );
// N, 1
// копируем результат работы device обратно на host (копия c)
cudaMemcpy( c, dev_c, size, cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
return 0;
}

Теперь рассмотрим код, который использует и несколько блоков и несколько нитей в каждом блоке:
__global__ void add( int *a, int *b, int *c ) {
int index = threadIdx.x + blockIdx.x * blockDim.x;
c[index] = a[index] + b[index];
}

#define N (2048*2048)
#define THREADS_PER_BLOCK 512
int main( void ) {
int *a, *b, *c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии of a, b, c
int size = N * sizeof( int );
//выделяем память на device для of a, b, c
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, size );
a = (int*)malloc( size );
b = (int*)malloc( size );
c = (int*)malloc( size );
random_ints( a, N );
random_ints( b, N );
//копируем ввод на device
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
//запускаем на выполнение add() kernel с блоками и нитями
add<<< N/THREADS_PER_BLOCK, THREADS_PER_BLOCK >>>( dev_a, dev_b, dev_c );
// копируем результат работы device на host ( копия c )
cudaMemcpy( c, dev_c, size, cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
return 0;
}

Нити на первый взгляд кажутся избыточными. Параллельные нити, в отличие параллельных блоков, имеют механизмы для, так называемых, коммуникации и синхронизации. Давайте рассмотрим этот вопрос.
Возьмем в качестве примера на этот раз не сложение, а скалярное произведение.
Нити в одном блоке могут иметь общую память shared memory. Эта память декларируется в коде ключевым словом __shared__. Эта память не видна в другом блоке.
Также нити можно "синхронизировать". То есть каждая нить в некой "точке синхронизации" будет ждать пока выполнение других нитей тоже не достигнет этой точки. Синхронизация нитей истинна только для нитей одного блока. Синхронизация вводиться функцией __syncthreads().
Вот код, с синхронизацией и использованием shared memory:
//__global__ void dot( int *a, int *b, int *c )   {
// Каждая нить вычисляет одно слагаемое скалярного произведения
// int temp = a[threadIdx.x] * b[threadIdx.x];
//Не может вычислить сумму
//Каждая копия ‘temp’ приватна для своей нити
//}

__global__ void dot( int *a, int *b, int *c ) {
__shared__ int temp[N];
temp[threadIdx.x] = a[threadIdx.x] * b[threadIdx.x];
__syncthreads();
if( 0 == threadIdx.x ) {
int sum = 0;
for( int i = 0; i < N; i++ )
sum += temp[i];
*c = sum;
}
}

#define N 512
int main( void ) {
int *a, *b, *c;
int *dev_a, *dev_b, *dev_c;
int size = N * sizeof( int );

cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, sizeof( int ) );
a = (int *)malloc( size );
b = (int *)malloc( size );
c = (int *)malloc( sizeof( int ) );
random_ints( a, N );
random_ints( b, N );
// копируем ввод на device
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
//запускаем на выполнение dot() kernel с 1 блоком и N нитями
dot<<< 1, N >>>( dev_a, dev_b, dev_c );
//копируем результат работы device на host копией c
cudaMemcpy( c, dev_c, sizeof( int ) , cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
return 0;
}

threadIdx.x используется для доступе к отдельной нити. threadIdx - это встроенная переменная. Для индексации вводимых/выводимых элементов используем выражение (threadIdx.x + blockIdx.x * blockDim.x).blockDim.x - это встроенная переменная, обозначающая число нитей в блоке.
В последнем примере использовался только один блок. А если нам надо использовать много блоков? Скалярное произведение же у нас тут только для примера.
__global__ void dot( int *a, int *b, int *c ) {
__shared__ int temp[THREADS_PER_BLOCK];
int index = threadIdx.x + blockIdx.x * blockDim.x;
temp[threadIdx.x] = a[index] * b[index];
__syncthreads();
if( 0 == threadIdx.x ) {
int sum = 0;
for( int i = 0; i < THREADS_PER_BLOCK; i++ )
sum += temp[i];
*c += sum;
}
}

В таком варианте функции dot у нас возникает race condition. Что это? Не тема этого поста. Смотрим википедию. С этой проблемой боремся с помощью атомарных функций. В данном случае - это atomicAdd.
__global__ void dot( int *a, int *b, int *c ) {
__shared__ int temp[THREADS_PER_BLOCK];
int index = threadIdx.x + blockIdx.x * blockDim.x;
temp[threadIdx.x] = a[index] * b[index];
__syncthreads();
if( 0 == threadIdx.x ) {
int sum = 0;
for( int i = 0; i < THREADS_PER_BLOCK; i++ )
sum += temp[i];
atomicAdd( c , sum );
}
}

Функция main будет такая:
#define N (2048*2048)
#define THREADS_PER_BLOCK 512
int main( void ) {
int *a, *b, *c; // host копии a, b, c
int *dev_a, *dev_b, *dev_c; // device копии a, b, c
int size = N * sizeof( int );
//выделяем место в памяти для device копий a, b, c
cudaMalloc( (void**)&dev_a, size );
cudaMalloc( (void**)&dev_b, size );
cudaMalloc( (void**)&dev_c, sizeof( int ) );
a = (int *)malloc( size );
b = (int *)malloc( size );
c = (int *)malloc( sizeof( int ) );
random_ints( a, N );
random_ints( b, N );
//копируем ввод на device
cudaMemcpy( dev_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( dev_b, b, size, cudaMemcpyHostToDevice );
//запускаем на выполнение dot() kernel
dot<<< N/THREADS_PER_BLOCK, THREADS_PER_BLOCK >>>( dev_a, dev_b, dev_c );
//копируем результат работы device обратно на host (копия c)
cudaMemcpy( c, dev_c, sizeof( int ) , cudaMemcpyDeviceToHost );
free( a ); free( b ); free( c );
cudaFree( dev_a );
cudaFree( dev_b );
cudaFree( dev_c );
return 0;
}

CUDA предоставляет атомарные функции, которые гарантируют "безопасность" read-modify-write операции.
Пример атомарных функций в CUDA:
atomicInc()
atomicAdd()
atomicDec()
atomicSub()
atomicExch()
atomicMin()
atomicCAS()
atomicMax()
С помощью этих примеров продемонстрированы приемы, которые можно использовать для применения в параллельных вычислениях на видеокартах NVIDIA.

воскресенье, 1 мая 2011 г.

Инсталляция драйвера NVIDIA в debian Squeeze

Для начала идем на http://packages.debian.org/ и выкачиваем следующие non-free пакеты:

nvidia-xconfig_*.deb
nvidia-libvdpau1_*.deb
nvidia-kernel-source_*.deb
nvidia-kernel-*.deb
nvidia-glx_*.deb
libglx-nvidia-alternatives_*.deb
libgl1-nvidia-glx_*.deb
libgl1-nvidia-alternatives_*.deb

Делаем им всем dpkg -i.
Либо добаляем в соответствующее место файла /etc/apt/sources.list слово non-free и делаем этим пакетам apt-get install.
Потом так:

# apt-get update
# apt-get install module-assistant nvidia-kernel-common build-essential nvidia-settings
# m-a clean nvidia-kernel-source
# m-a purge nvidia-kernel-source
# m-a prepare
# m-a a-i nvidia-kernel-source
# nvidia-xconfig

перезагружаемся и имеем что хотели.

суббота, 16 апреля 2011 г.

Простейшее применение Eclipse CDT в windows 7

Рассмотрим создание простейшего приложения, написанного на языке си, с помощью IDE Eclipse CDT в среде windows 7.
Для начала проинсталлируем саму IDE. Идем на домашнюю страницу проекта www.eclipse.org/cdt и выкачиваем её. Для установки достаточно распаковать выкачанный пакет в подходящую для вас директорию. В ней будет исполняемый файл для запуска этой IDE.
Если первом запускее выбросится такое сообщение:

то это значит необходимо установить java. Идем на сайт java и качаем её к себе на компьютер:

и инсталлируем:



После этого Eclipse должен запуститься. Будет задан вопрос, какое расположение рабочего пространства вас устраивает:

Ну и далее успешный запуск Eclipse сопровождается такими видами:



Далее создаём проект. Находим в меню new project:

Выбираем Makefile project with existing code:

Создаем пустой исходный файл:


а также makefile:


Также необходимо настроить свойства проекта (предполагается наличие установленнного MinGW, если не установлен смотрите в этом же блоге, как это можно сделать):



Также необходимо настроить цели для make-файла:

Ну а далее компиллируем исходник:

и запускаем на выполнение полученный код:

и видим результат:

Несколько замечаний напоследок.
Есть проект wascana, который "из коробки" дает вышеописанную фунциональность, но так как MinGW и Eclipse CDT это различные продукты, друг от друга независяшие, то я думаю имеет смысл не доверяться другим людям, а настроить самому. Это полезно для понимания того что происходит. Да и, как мы увидели, эта настройка предельно тривиальна.

воскресенье, 20 марта 2011 г.

Рекомендуемые для бэкапа директории и файлы в Debian

Кроме собственно ваших рабочих файлов, будет черезвычайно полезно держать в безопасном месте содержимое следующих директорий:
/etc
/var/lib/dpkg

Также необходимо сохранить файл:
/var/lib/apt/extended_states
и вывод команды dpkg --get-selections "*"
если используется aptitude, то сохраняем и файл /var/lib/aptitude/pkgstates
Пакеты, которые установлены в системе хранятся в /var/cache/apt/archives/
Файлы нужныйе для работы apt-file лежат в /var/cache/apt/apt-file

Создание массива из имен файлов в командной стрке bash

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


DIR="/path/to/dir"
IFS=$'\n'
DirsArray=($(find $DIR -maxdepth 1 -type f))
DirsLenght=${#DirsArray[@]}
for (( i=0; i<${DirsLenght}; i++ ));
do
echo "${DirsArray[$i]}"
done
for j in $(seq 0 $((${#DirsArray[@]} - 1))) ; do
echo $j ":" ${DirsArray[$j]}
done

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

Запись части монитора на видеоролик

Используем для этого утилиту ffmpeg.
#ffmpeg -f x11grab -r 10 -s 500x440 -i :0.0+200,200 -vcodec ffv1 Desktop/output.avi
+200,200 - это координаты верхнего левого угла отслеживаемой области экрана в пикселах;
500x440 - размеры отслеживаемой области в пикселах;
10 - число кадров в секунду.
Чтобы конвертировать в другой формат видео можно использовать команду типа такой:
#ffmpeg -i Desktop/output.avi -vcodec qtrle Desktop/output.mov
В данном примере все действия выполнялись из домашней директории пользователя на компьютерах с Debian squeeze и lenny. Файлы с видео записывались на десктоп.

среда, 2 февраля 2011 г.

Пример простейшего применения Glade для создания графического интерфейса приложения

При использовании Glade значительно сокращается объем кода. Код, который соответствует собственно описанию интерфейса, практически полностью "переезжает" в xml-файл, который можно "подсоединить" вызовом функции gtk_builder_add_from_file. Упомянутый xml-файл создается в Glade.
Здесь продемонстрируется минимальный код приложения, которое показывает в главном окне всего два виджета: GtkEntry и GtkLabel. При вводе некоего текста в GtkEntry этот текст будет отображаться в GtkLabel.
Создание соответствующего интерфейса в Glade тривиально и больших комментариев не требует. Запускаем Glade.

Оставляем как есть. Жмем Close.
Далее создаем окно приложения:

Добавляем в него вертикальный ящик с тремя ячейками:

Далее вставляем в эти ячейки наши GtkEntry и GtkLabel, а также кнопку выхода из приложения:


Для обработки сигнала clicked кнопки выхода выбираем стандартный gtk_main_quit. А для обработки сигнала changed (находим его в GtkEditable) выбираем любой предлагаемый вариант (не возбраняется и свой вариант набрать). Выберем on_entry1_changed. Не забываем потом написать код для этой функции.
А вот собственно код нашего приложения:

/*labelAndEntry.c*/
#include <gtk/gtk.h>

typedef struct _ddata ddata;
struct _ddata{
GtkWidget* window1;
GtkWidget* entry1;
GtkWidget* label1;
GtkWidget* button1;
} ;

ddata* myData;

int
main( int argc, char **argv )
{
GtkBuilder *builder;
GtkWidget *window;
GError *error = NULL;

/* инициализируем GTK+ */
gtk_init( &argc, &argv );

/* создаем новый объект типа GtkBuilder */
builder = gtk_builder_new();
/* загружаем пользовательский интерфес из файла, который мы создали в Glade */
if( ! gtk_builder_add_from_file( builder, "entryAndLabel.xml", &error ) )
{
g_warning( "%s", error->message );
g_free( error );
return( 1 );
}

myData = g_slice_new(ddata);
myData->window1 = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );
myData->entry1 = GTK_WIDGET( gtk_builder_get_object( builder, "entry1" ) );
myData->label1 = GTK_WIDGET( gtk_builder_get_object( builder, "label1" ) );
myData->button1 = GTK_WIDGET( gtk_builder_get_object( builder, "button1" ) );
/*
заметим, что имена полей структуры myData не обязаны совпадать со вторым аргументом функции gtk_builder_get object, они произвольны. Но этот второй аргумент должен совпадать с именем виджета, которое мы задали ему в окне "Doc Properties" Glade во вкладке General
*/
/* Get main window pointer from UI */
// window = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );

/* "связываем" сигналы с объектами графического интерфейса */
// gtk_builder_connect_signals( builder, NULL );
gtk_builder_connect_signals( builder, myData );
/* разрушаем объект builder, так как он нам больше не нужен */
g_object_unref( G_OBJECT( builder ) );

/* даем команду показать главное окно приложения, а остальные виджеты отображаются объектом GtkBuilder */
gtk_widget_show( myData -> window1 );

/* стартуем */
gtk_main();
g_slice_free(ddata, myData);
return( 0 );
}
void
on_entry1_changed (GtkEditable *editable,
gpointer user_data) {
puts("changed!");
gtk_label_set_text(GTK_LABEL(myData->label1), gtk_entry_get_text(GTK_ENTRY(editable) ));
}
/*
имя этой функции должно совпадать с именем, которое мы дали обработчику соответствующего события (у нас - changed) в окне "Doc Properties" и вкладке Signals.
*/

Компилируем:
$ gcc -o labelAndEntry labelAndEntry.c $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0)
Запускаем на выполнение. Должно получиться следующее:

Если запустить наше прилодение из командной строки, то также при вводе какого-либо символа в GtkEntry, в консоли будет выводиться сообщение "changed!". При нажатии на кнопку будет осуществлен выход.
Все вышеописанное выполнялось в Debian squeeze.

понедельник, 31 января 2011 г.

Классы С++

Этот текст является конспектом глав "классы", "производные классы", "иерархии классов" книги Страуструпа "язык программирования С++" и предназначен для упрощения работы по повторному прочтению этих глав. Чтобы быстрее вспомнить, про что там говорилось. Поэтому этот материал возможно будет полезен только тем, кто уже хорошо знаком с понятием класса С++ и уже с ним работал, но не помнит всех подробностей и тонкостей (что в общем то, наверно, и невозможно), а внимательно читать весь текст упомянутых глав накладно с точки зрения времени. В то же время я постарался выдернуть максимум информации, чтобы материал был полным. Изложение носит чисто качественный характер, без кусков кода и примеров, иначе это изложение было бы просто переписанными главами из книги, что противоречило бы поставленной цели. Буду благодарен за любые замечания и комментарии.
Целью введения классов является предоставление средств создания новых типов, которые настолько же удобны в использовании, как и встроенные. Тщательно подобранный набор типов, определяемых пользователем, делает программу более краткой и выразительной.
"Конкретные" типы, определяемые пользователем, с логической точки зрения незначительно отличаются от встроенных типов. В идеале, такие типы должны отличаться от встроенных не по методам их использования, а только способом их создания.
Класс - это определяемый пользователем тип.
Конструкция
class X {...};
называется определением класса, потому что она определяет новый тип. По историческим причинам определение класса часто называют объявлением класса. Также как и объявления, не являющиеся определениями, определение класса может быть повторено в другом исходном классе при помощи #include, не нарушая правила одного определения ODR.
Во время проектирования класса возникает искушение добавить средства просто потому, что они кому-нибудь могут понадобиться. Требуется основательно подумать, какие средства нужны на самом деле, и включать только их. Эти усилия приводят к большей понимаемости программы и уменьшают её размер. Одним из способов уменьшения количества похожих друг на друга функций является использование аргументов по умолчанию. Когда значение аргумента используется для указания "вставить значение по умолчанию", оно должно находиться вне пределов допустимых "обычных" значений этого аргумента.
Переменная, которая является частью класса, но не является частью объекта этого класса, называется статическим членом. Существует ровно одна копия статического члена. Аналогично, функция, которой требуется доступ к членам класса, но не требуется, чтобы она вызывалась для конкретного объекта класса, называется статической функцией-членом.
К статическому объекту можно можно обращаться без указания имени объекта. Вместо этого в качестве квалификатора его имени используется имя самого класса.
Статические члены - и функции и данные - должны быть где-то определены.
По умолчанию, объекты класса можно копировать. Объект некоторого класса можно проинициализировать при помощи копирования объекта того же класса. Это можно сделать даже там, где объявлен конструктор. По умолчанию, копия объекта класса содержит копию каждого члена. Если это не то что требуется, можно реализовать более подходящее поведение, определив копирующий конструктор
X::X(const X&)
Аналогично, объекты класса могут по умолчанию копироваться при помощи операции присваивания. Семантика по умолчанию - почленное копирование. Пользователь может определить свой оператор присваивания.
Суффикс const является частью типа константных функций-членов. То есть, когда константная функция-член определяется вне класса, суффикс const обязателен.
В нестатической функции-члене ключевое слово this является указателем на объект, для которого вызвана функция. В нестатической функции-члене класса Х this имеет тип X*. Это не обычная переменная. Невозможно получить её адрес или присвоить ей что-нибудь. В константной функции члене класса X this имеет тип const X* для предотвращения модификации самого объекта.
Иногда функцию-член необходимо сделать константной, но для необходимой функциональности все же требуется менять части объекта (не заметно для пользователя). В таком случае говорят о логическом и физическом постоянствах. Логически функция-член константна, физически - нет.
Если есть необходимость написать константную функцию-член класса Х, но которой все же необходимо изменить некоторые логически "скрытые" члены класса, можно использовать конструкцию const_cast<X*>(this). Однако поведение будет неопределённым, если объект объявлен константой. Чтобы избежать явного преобразования типа "снятие const путем приведения" можно при объявлении соответствующих членов класса использовать квалификатор хранения mutable. mutable означает: "ни при каких условиях не является const".
По определению структура есть класс, все члены которого по умолчанию являются открытыми. Страуструп предпочитает использовать структуры для классов, у которых все данные открыты. Он думает о таких классах как о "не совсем типах, являющихся просто структурами данных". Конструкторы и функции доступа к ним могут быть полезны для таких структур, но скорее для удобства, а не как гаранты свойств типа.
Имеет смысл помещать данные класса в конце, чтобы сделать акцент на функциях открытого интерфейса.
Модификаторы доступа (private, public) можно использовать несколько раз в одном и том же объявлении. Это может запутать программиста , но полезно при автоматической генерации кода.
Функция, определённая в пределах определения класса, является встроенной функцией членом. Функция, объявленная в классе, может обращаться к любому члену класса так, как будто весь класс был полностью определен до рассмотрения тела функции. Однако это может быть неочевидно для читающего программу. Поэтому можно определять встраиваемые (inline) функции после класса.
Набор операций, характерный для типа, определяемого пользователем:
  • Конструктор, определяющий как должны быть проинициализированы переменные данного типа

  • Набор функций доступа (функций-селекторов). Эти функции имеют модификатор const

  • Набор функций-модификаторов. При их использовании не должно возникать необходимости разбираться в деталях представления

  • Набор неявно определённых операций, позволяющих свободно копировать объекты

  • Класс, используемый для сообщений об ошибках путём возбуждения исключений

Инициализация является относительно сложной операцией, так как включает в себя проверку корректности данных. Это позволяет пользоваться и копировать созданный класс без дополнительных проверок. Конструктор устанавливает инвариант класса, а другие функции-члены могут полагаться на этот инвариант и должны сохранять его.
Как правило, у класса есть набор функций, связанных с ним, но не требующих определения их в классе, так как они не нуждаются в непосредственном доступе к представлению. Такие функции-помощники объявляются в заголовочном файле класса либо помещаются в одно пространство имён с классом или классами. Использование пространства имён для одного класса обычно является избыточным.
Пользователю должен быть предоставлен механизм определения небольших конкретных типов. Слово "конкретный" нужно для того, чтобы отличать такие типы или классы от абстрактных классов и иерархий классов, а также, чтобы подчеркнуть их сходство со встроенными типами, такими как int или char. Конкретные типы называют ещё типами значений, а их использование - программированием, ориентированным на значения. Модель использования и проектирование конкретных типов сильно отличается от того, называется ООП. В частности, конкретные типы не демонстрируют полиморфное поведение. Хороший набор конкретных типов может создать фундамент приложения.
Конструкторы инициализируют объект. Они создают среду. в которой работают функции-члены. Требуется функция, которая будет гарантировано вызвана при уничтожения объекта. Такая функция называется деструктором. Деструктор вызывается неявно, когда автоматическая переменная выходит из области видимости, когда удаляется объект, находящийся в свободной памяти и т.д. Только в очень необычных ситуациях пользователю требуется явно вызывать деструктор.
Комплиментарные пары конструктор/деструктор являются типичным механизмом в С++ для выражения понятия объекта переменного размера. К типам без деструктора можно относится как типы, деструкторы которых ничего не делают.
Аналогично можно полагать, что многие типы имеют конструктор по умолчанию. Конструктор по умолчанию есть конструктор, вызываемый без аргумента. Если пользователь объявил конструктор по умолчанию, то он и будет задействован. В противном случае (и если пользователь не объявил другие конструкторы) компилятор попытается при необходимости сгенерировать его. Конструктор по умолчанию, сгенерированный компилятором, неявно вызывает конструкторы по умолчанию для членов класса и конструкторы базовых классов.
Если тип члена класса не является классом - он не инициализируется.
Константы и ссылки должны быть проинициализированы, а потому класс, содержащий члены, являющиеся константами или ссылками, не может быть сконструирован по умолчанию, если программист не предоставил явно конструктор.
Конструкторы по умолчанию можно вызывать явно.
Встроенные типы имеют конструкторы по умолчанию.
Классификация объектов способам создания и уничтожения:
  • Именованный автоматический объект

  • Объект в свободной памяти

  • Нестатический член-объект

  • Элемент массива

  • Локальный статический объект

  • Глобальный объект, объект в пространстве имен, статический объект класса

  • Временный объект-часть вычисления выражения

  • Объект, помещённого в память, выделенную функцией пользователя

  • Член объединения union, который не может иметь ни конструктора, ни деструктора

Конструктор локальной переменной вызывается когда управление передается инструкции, содержащей объявление этой локальной переменной. Деструктор - когда происходит выход из блока, содержащего это объявление. Деструкторы локальных переменных вызываются в порядке обратном выполнению конструкторов.
Если у класса есть члены, которые являются указателями, то при копировании объектов этого класса из-за "почленности" копирования может быть не тот результат, что ожидается. Поэтому для таких классов надо обязательно определять, что понимается под копированием, то есть задать копирующий конструктор и копирующее присваивание. Копирующий конструктор и копирующее присваивание значительно отличаются. Основная причина - это то, что копирующий конструктор инициализирует "чистую" память, а копирующий оператор присваивания доложен правильно работать с уже созданным объектом.
Основная стратегия при реализации оператора присваивания:
  • защита от присваивания самому себе

  • удаление старых элементов

  • инициализация и копирование новых элементов

Для объекта, создаваемого в свободной памяти, вызывается конструктор класса, указанный в операторе new. Вызов оператора delete для одного и того же объекта является ошибкой. Если вызова для объекта в свободной памяти оператора delete не будет, то возможна утечка памяти. Некоторые реализации имеют сборщики мусора, но их поведение не стандартизовано. Если известно, что имеется сборщик мы можем во избежание двойного удаления не использовать оператор delete, но тогда не будет переносимости кода. После применения к объекту delete обращение к нему - это ошибка. Реализации надёжно не отслеживают такие ошибки.
Пользователь сам может определить, каким образом new выделяет память и как delete её освобождает. Кроме того можно указать способы взаимодействия выделения памяти, инициализации(конструирования) и исключений.
Аргументы конструкторов-членов указываются в списке инициализации членов в определении конструктора объемлющего класса. Конструкторы членов класса вызываются до вызова конструктора самого класса. Конструкторы вызываются в том порядке, в котором они объявлены в классе, а не в том, в котором они указаны в списке инициализации. Поэтому во избежание путаницы порядок объявлений в классе и порядок в списке инициализации должны соответствовать (за этим должен следить программист). Деструкторы членов вызываются в порядке обратном порядку конструкторов и после вызова деструктора самого класса.Если конструктор члена не нуждается в аргументах, то его можно не указывать в списке инициализации.
Члены объекта, у которых присваивание и инициализация отличны (объекты-члены без конструктора по умолчанию, константные члены, члены ссылки) должны быть проинициализированы конструктором объемлющего класса (в списке инициализации). Отсутствие инициализации объектов такого типа является ошибкой.
Для большинства типов мы имеем выбор между использованием инициализатора и присваиванием. Желательно пользоваться синтаксисом инициализации, делая явным факт инициализации.
Проинициализировав статический константный член класса внутри класса(определение статического члена делается вне класса) необходимо помнить, что при определении его нельзя инициализировать ещё раз.
Копирующие конструкторы по умолчанию и копирующие операторы присваивания по умолчанию копируют все элементы класса. Если такое копирование не может быть выполнено, то попытка копирования объекта класса будет ошибкой. Присваивание по умолчанию не может быть сгенерировано, если нестатический член класса является ссылкой, константой или типом без копирующего оператора присваивания.
При добавлении нового члена класса всегда проверяйте, не надо ли модифицировать определяемые пользователем конструкторы с учётом инициализации и копирования нового члена.
Не существует способа явного указания аргументов конструктора (за исключением списка инициализации) при объявлении массива.
Деструктор для каждого элемента массива вызывается при уничтожении массива. Это происходит неявно для массивов, память для которых не выделялась с помощью new. И в си и в с++ указатели на отдельный объект и на первый элемент массива не различаются. Поэтому программист должен указать, удаляется ли массив или отдельный объект (delete, delete[]). Как выделяется память, зависит от реализации. Поэтому реакция на неверное использование delete или delete[] будет различной. Компилятор подобную ошибку в простейшем случае находить самостоятельно, но, как правило, неприятности из-за такой путаницы происходят во время исполнения.
Вместо массивов в стиле си желательно пользоваться классом, подобным vector.
Конструктор локального статического объекта вызывается один раз при первом выполнении инструкции, содержащей определение объекта. Деструкторы локальных статических объектов выполняются в порядке, обратном созданию, при завершении программы. В какой момент точно - не определено.
Переменная, определенная вне любой функции(глобальная, объявленная в пространстве имен или статически в классе) инициализируется (конструируется) до вызова main(). После выхода из main() будет вызван деструктор для каждой такой переменной. Динамическая компоновка усложняет ситуацию, откладывая инициализацию до того момента, когда код будет скомпонован в исполняемый код. Конструкторы нелокальных объектов в единице трансляции выполняются в процессе их определений. Объявления типов не влияют на порядок создания объектов. Деструкторы вызываются в порядке, обратном конструкторам.
Не дается никаких гарантий по поводу порядка конструирования (а также вызова деструкторов) нелокальных объектов из разных единиц компиляции. Это будет зависеть от реализации. Причём не гарантируется один и тот же порядок в одной и той же реализации.
Если временный объект не связан со ссылкой или не используется для инициализации именованного объекта, он уничтожается по достижении конца полного выражения, в котором был создан. Полное выражение - это выражение, которое не является частью другого выражения.
Временная переменная может использоваться в качестве инициализатора константной ссылки или именованного объекта.
Надо помнить, что возврат из функции ссылки на локальную переменную является ошибкой, и что неконстантная ссылка не может ссылаться на временную переменную.
Мы можем поместить объекты куда угодно, написав функцию выделения памяти, имеющую дополнительные аргументы, и затем задавая эти аргументы при использовании оператора new:
void* operator new(size_t, void* p){return p;}
void* buf = reinterpret_cast<void*>(0xFFFF);
X* p2 = new(buf)X;
Из-за своего использования синтаксис new(buf)X называется синтаксисом размещения.
Размер размещаемого объекта задаётся неявно. Выбор функции operator new() для вызова оператора new подчиняется обычным правилам соответсятвия аргументов. Каждый operator new() имеет в качестве первого аргумента size_t.
В большинстве случаев в результате применения reinterpret_cast получается значение нужного типа с той же последовательностью битов, что и его аргумент. Этим преобразованием пользуются для опасных, зависящих от реализации, но необходимых преобразований целых значений в указатели и наоборот.
Базовый класс иногда называют суперклассом, а производный подклассом. Однако лучше такой терминологией не пользоваться, так как данные в объекте производного класса являются надмножеством данных базового класса.
Если производный класс Derived имеет базовым открытым классом Base, то Derived* может быть присвоен переменной типа Base* без явного преобразования типа. Обратное преобразование должно быть явным (с помощью static_cast и dynamic_cast). То есть с объектом производного класса можно обращаться как с объектом базового класса при обращении к нему при помощи указателей и ссылок. Обратное не верно.
Член производного класса может пользоваться открытыми (и защищёнными) членами базового класса так, как будто они объявлены в самом производном классе.. Производный класс не может использовать закрытые имена базового класса. Защищённый член ведёт себя как открытый по отношению к члену производного класса и как закрытый по отношению к другим функциям.
Некоторый производные классы нуждаются в конструкторах. Если базовый класс имеет конструктор, он должен быть вызван. Конструкторы по умолчанию могут быть вызваны неявно. Однако, если все конструкторы базового класса требуют указания аргументов, то конструктор этого базового класса должен быть вызван явным образом. Аргументы конструктора базового класса указываются в определении конструктора производного класса. В этом отношении базовый класс ведет себя как член производного класса.
Объекты класса создаются снизу вверх: сначала базовый класс, потом члены и затем сам производный класс. Они уничтожаются в противоположном порядке: сначала производный класс, члены, а затем базовый класс. Члены и подобъекты базовых классов конструируются в порядке их объявления в классе и уничтожаются в обратном порядке.
Копирование объектов класса определяется копирующими конструктором и операторами присваивания.
Копирующая функция базового класса ничего не знает о производном классе. Поэтому при копировании объекта производного класса в объект базового класса происходит так называемая "срезка". Одной из причин передачи указателей и ссылок на объекты в иерархии является желание избежать срезки. Другой причиной является обеспечение полиморфного поведения.
Если не определить оператор копирующего присваивания, он будет сгенерирован компилятором. Отсюда - операторы присваивания не наследуются. Конструкторы никогда не наследуются.
Пусть имеется указатель на некий класс. Этот указатель не обязательно будет указывать на объект именно этого класса, но может и на объект любого класса потомка этого некоего класса в иерархии. Как определить, какому производному типу принадлежит объект на самом деле? Существует четыре фундаментальных решения этой проблемы:
  • Гарантировать, что ссылки осуществляются только на объекты одного типа

  • Поместить поле типа в базовый класс

  • Использовать динамическое преобразование типа dynamic_cast

  • Использовать виртуальные функции

Виртуальные функции решают проблему, связанную с полем типа, предоставляя возможность программисту объявлять в базовом классе функции, которые можно заместить в каждом производном классе.
Для того чтобы объявление виртуальной функции работало в качестве интерфейса к функциям, определённым в производных классах, типы аргументов функции в производном классе не должны отличаться от типов аргументов, объявленных в базовом классе, и только очень (!) небольшие изменения допускаются для типа возвращаемого значения.
Виртуальную функцию иногда называют методом.
Виртуальная функция должна быть определена для класса, в котором она впервые объявлена.
Виртуальную функцию можно использовать, даже если у её класса нет производных классов.
Производный класс, который не нуждается в собственной версии виртуальной функции, не обязан её реализовывать.
Говорят, что функция из производного класса с тем же именем и тем же набором типов аргументов, что и виртуальная функция в базовом классе, замещает (override) виртуальную функцию из базового класса.
За исключением случаев, когда мы явно указываем, какая версия виртуальной функции должна вызываться, активизируется наиболее подходящий для вызывающего объекта вариант замещённой функции.
Тип, имеющий виртуальные функции, называется полиморфным типом.
Для достижения полиморфного поведения в С++ вызываемые функции-члены должны быть виртуальными, и доступ к объектам должен осуществляться через ссылки и указатели. При непосредственном манипулировании объектом (без указателя или ссылки на него) его точный тип известен компилятору, и потому полиморфизм времени выполнения не требуется.
Некоторые классы представляют абстрактную концепцию, для которой объекты не существуют. В таких классах нет данных. Также нету конструктора, так как нет того, что надо инициализировать. Но нужен виртуальный деструктор, чтобы гарантировать правильную очистку данных, которая будет определена в производных классах.
Виртуальные функции можно объявлять в виде чисто виртуальных функций. Виртуальная функция делается чистой при помощи инициализатора =0. Класс с одной или несколькими чисто виртуальными функциями называется абстрактным классом.
Чисто виртуальная функция, которая не определена в производном классе, остается чисто виртуальной, а потому такой класс также является абстрактным.
Оператор delete явным образом уничтожает объект, но нет способа точно определить, к какому классу принадлежит объект. Однако благодаря вируальному деструктору будет вызван нужный деструктор.
Абстрактный класс с набором операций конструирования иногда называют фабрикой, а его функции иногда называют виртуальными конструкторами.
В общем случае, класс создаётся из решётки базовых классов. Исторически большинство таких решёток были деревьями, поэтому решётку классов часто называют иерархией классов. Использование более одного непосредственного базового класса обычно называется множественным наследованием. Наличие одного базового класса называется одиночным наследованием.
Два базовых класса могут иметь функции-члены с одинаковым именем. Неоднозначности для этих функций в производных классах можно устранить с помощью ::.
Разрешение перегрузки не пресекает границ областей видимости классов. В частности, неоднозначности между функциями из различных базовых классов не разрешаются на основе типов аргументов. Объявления using позволяют создавать набор перегруженных функций из базовых и производных классов.
using-объявление в определении класса должно относиться к членам базового класса. using-объявление нельзя использовать для члена класса вне этого класса, его производных классов или их функций членов. using-директиву нельзя поместить в определение класса, и она не может использоваться для класса. using-объявление не может использоваться для доступа к дополнительной информации. Оно просто является механизмом предоставления более удобного доступа к информации, доступ к которой разрешён в принципе.
Виртуальная функция повторяющегося базового класса может быть замещена (единственной) функцией в производном классе.
В решётке наследования все виртуальные базовые классы с одним именем будут представлены одним единственным объектом этого класса. Невиртуальный базовый класс в этой же решётке будет иметь отдельный подобъект, представляющий его.
Конструктор виртуального базового класса вызывается только один раз. Он вызывается (явно или неявно) из конструктора объекта (конструктора самого "нижнего" производного класса). Конструктор виртуального базового класса вызывается до конструкторов производных классов.
Если различные производные классы замещают одну и ту же функцию, то если у этих классов имеются свои производные классы, то в них мы должны заместить эту функцию.
Член класса может быть закрытым, защищённым, открытым (1)private, 2)protected, 3)public):
1)Его имя может использоваться только в функциях-членах и друзьях класса, в котором он объявлен;
2)Его имя может использоваться только в функциях-членах и друзьях класса, в котором он объявлен, и классов, производных от него;
3)Его именем может пользоваться любая функция;
Объявление данных защищёнными обычно свидетельствует об ошибке на этапе проектирования. Защищённые данные приводят к проблемам сопровождения. Члены класса по умолчанию закрыты, что, как правило, является наилучшим вариантом. Эти возражения против защищённости не имеют значения для защищённых функций. Защищённость - это прекрасный способ задания операций для использования в производных классах.
class D:pivate B{/* ... */};
Спецификатор доступа может быть опущен. Тогда private будет по умолчанию.
Спецификатор доступа к базовому классу управляет доступом к членам базового класса и преобразованием указателей и ссылок из типа производного класса в тип базового класса.
  • Если базовый класс является закрытым, то его открытые и защищённые члены могут быть использованы только функциями-членами и друзьями производного класса. Только друзья и члены производного класса могут преобразовывать указатель на объект производного класса в указатель на объект базового класса

  • Если базовый класс защищённый, то его открытые и защищённые члены могут быть использованы только функциями-членами и друзьями производного класса и его производных классов. Только друзья и члены производного класса и его производных классов могут преобразовывать указатель на объект производного класса в указатель на объект базового класса

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

Если доступ к имени или базовому классу может быть осуществлён при помощи нескольких путей в решётке классов, то доступ разрешён только в том случае, если он разрешён по каждому из возможных путей.
dynamic_cast - это операция преобразования типа, которая возвращает корректный указатель, если объект имеет ожидаемый тип, и нулевой указатель - в противном случае.
Использование информации о типе во время выполнения обычно называют RTTI (Run-Time Type Information).
Приведение из базового класса в производный часто называют понижающим приведением, а из производного класса в базовый - повышающим приведением (downcast и upcast), так как принято изображать дерево наследования растущим вниз из корня наверху. Приведение между производными классами одного базового класса называют перекрёстным приведением (crosscast).
dynamic_cast не допускает случайных нарушений правил доступа к закрытым и защищённым базовым классам.
dynamic_cast используется тогда, когда правильность преобразования не может быть определена компилятором.
Для выполнения понижающего или перекрёстного приведения требуется, чтобы аргумент dynamic_cast ,был ссылкой или указателем на полиморфный тип.
Если у объекта нет виртуальных функций, им нельзя безопасно манипулировать, не зная его конкретный тип. Если его тип неизвестен, то нет необходимости в использовании dynamic_cast.
Результирующий тип dynamic_cast не обязан быть полиморфным. Это позволяет "заворачивать" конкретный тип в полиморфный, а затем извлекать объект этого конкретного типа обратно.
Приведение к void* с помощью dynamic_cast можно использовать для определения адреса начала объекта полиморфного типа:
void* p1=dynamic_cast<void*>(p2);
где p1 - адрес размещения объекта.
Это полезно при взаимодействии с низкоуровневыми функциями.
В круглых скобках в dynamic_cast должен быть указатель на объект только полиморфного типа.
Результат применения dynamic_cast к указателю нужно всегда явно проверять. Для указателя приведение dynamic_cast можно интерпретировать как вопрос "объект, на который указывает этот указатель, имеет этот тип?". Если dynamic_cast применяется к ссылке, то приведение dynamic_cast является не вопросом, а утверждением "объект, на который указывает этот указатель, имеет этот тип.", а потому при приведении dynamic_cast в случае, если приводимая ссылка является ссылкой на объект неожидаемого типа, то генерируется стандартное исключение bad_cast. Если надо защититься от неудачных приведений к ссылке, необходимо предоставить подходящий обработчик.
Статическое приведение static_cast не анализирует объект, который оно приводит.
Объект типа с ограничениями на способ размещения в памяти (задаваемый, например, языками си или фортран) может использоваться в качестве виртуального базового класса. Для таких объектов доступна только статическая информация о типе.
Существует миллионы строк кода. написанные до появления dynamic_cast.
При использовании dynamic_cast накладные расходы невелики.
Динамическое приведение более безопасно.
Компилятор не может делать предположений о памяти, на которую показывает void*. оэтому динамическое приведение не может осуществить приведение из *void. В этом случае требуется статическое приведение.
Приведения dynamic_cast и static_cast учитывают модификатор const и управление доступом.
Невозможно осуществить приведение в закрытый базовый класс, а "снятие const" (или volatile) требует const_cast. И даже в этом случае использование результата безопасно только в том случае, если объект не был изначально объявлен константным (или volatile).
Создание объекта идёт "снизу вверх", уничтожение "сверху вниз". При этом объект класса является объектом в той мере, в какой он был создан или уничтожен. Неразумно полагаться на порядок создания и уничтожения, но тем не менее этот порядок можно наблюдать при помощи вызовов виртуальных функций, динамического приведения или использования typeid в момент, когда объект ещё не завершён. Лучше не вызывать виртуальные функции на этапе создания или уничтожения.
dynamic_cast удовлетворяет большинство потребностей в информации о типе объекта на этапе выполнения. dynamic_cast сохраняет гибкость и способность к расширению, присущую виртуальным функциям. Но иногда надо знать не "принадлежит ли объект данному типу", а "каков точный тип объекта". Для этой цели служит оператор typeid. Он выдает объект, представляющий тип его операнда. typeid() возвращает ссылку на тип стандартной библиотеки, называемый type_info, определённый в <typeinfo>.
Оператор typeid() наиболее часто используется для нахождения типа объекта, на который указывает указатель или ссылка.
Если значение указателя или ссылки полиморфного типа равно 0, type_info() генерирует исключение bad_typeid. Если операнд type_info() имеет неполиморфный тип или не является lvalue, результат определяется на этапе компиляции без вычисления выражения операнда.
Не гарантируется, что только один объект type_info() существует для каждого типа в системе. Поэтому необходимо использовать == с объектами type_info для проверки равенства, а нес указателями на такие объекты.
Пример использования имени класса:
cout<<typeid(*p).name();
Символьное представление имени класса зависит от реализации Это С-строка находящаяся в системной области памяти, а потому нельзя пытаться её уничтожить при помощи delete[].
Как правило, следует избегать использование switch-инструкции.
С++ предлагает средство косвенной ссылки на член класса. Указатель на член является значением, идентифицирующим член класса. Его можно рассматривать как позицию члена в объекте класса (но компилятор принимает в расчёт различия между данными, виртуальными функциями, невиртуальными функциями и т.д.).
typedef void (ourClass::* memberPointerType)();//тип указателя на функцию-член
memberPointerType memberPointer = &ourClass::memberFunction;
objectPointer -> memberFunction();//прямой вызов
(objectPointer ->* memberPointer)();//вызов через указатель на член
Производный класс по крайней мере содержит члены, которые он унаследовал от базовых классов. Также он может содержать дополнительные члены. Из этого следует, что можно безопасно присвоить указатель на член базового класса указателю на член производного класса, но не наоборот. Это свойство называют контр вариацией.
Создаётся впечатление, что это правило противоречит правилу, что можно присваивать указатель на производный класс указателю на его базовый класс. В действительности оба правила нужны для фундаментальной гарантии того, что указатель ни при каких условиях не укажет на объект, который не реализует (по крайней мере) свойств, подразумеваемых типом этого указателя.
Можно самостоятельно управлять памятью для класса, определив собственные operator new() и operator delete() для индивидуальных объектов. operator new[]() и operator delete[]() играют ту же роль для массивов. Возможна также замена глобальных operator new() и operator delete(). Более надёжный подход состоит в реализации этих операций для конкретного класса. Эта реализация будет справедлива также и для производных классов.
Операторы new() и delete() неявно являются статическими членами. Следовательно у них нет указателя this и они не изменяют объект.
Добавление виртуального деструктора к базовому классу облегчает задачу определения истинного размера уничтожаемого объекта оператором operator delete(). Иначе при удалении объекта с потерянным типом (например, в результате приведения) будет нужна помощь программиста (как и в случае с удалением массива).
Если необходимо иметь пару для выделения/освобождения памяти, которая корректно работает с производными классами, то приходится предоставить виртуальный деструктор в базовом классе, либо воздержаться от использования аргумента типа size_t при освобождении памяти.
Может ли конструктор быть виртуальным? Нет. Но желаемое поведение можно получить. Чтобы создать объект конструктор должен знать тип создаваемого объекта. Поэтому он не может быть виртуальным. Конструктор является не совсем обычной функцией. Конструктор работает с памятью способом недоступным обычным функциям-членам. Нельзя получить указатель на конструктор. Можно создать функцию, которая вызывает конструктор и возвращает созданный объект.