С++ Интересные фишки

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#19 dyvniy » Пт, 18 марта 2016, 09:43:07

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

#pragma pack (push, 1)
deftype struct {...}
#pragma pack ( pop) 

http://www.kalinin.ru/programming/cpp/31_07_00.shtml
Спойлер
Запись структур данных в двоичные файлы

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

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

Итак, как все это выглядит обычно? Имеется некоторая структура данных:

struct data_item
{
type_1 field_1;
type_2 field_2;
// ...
type_n field_n;
};

data_item i1;
Каким образом, например, сохранить информацию из i1 так, что бы программа во время своего повторного запуска, смогла восстановить ее? Наиболее частое решение следующее:

FILE* f = fopen("file", "wb");
fwrite((char*)&i1, sizeof(i1), 1, f);
fclose(f);
assert расставляется по вкусу, проверка инвариантов в данном примере не является сутью. Тем не менее, несмотря на частоту использования, этот вариант решения проблемы не верен.

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

FILE* f = fopen("file", "rb");
fread((char*)&i1, sizeof(i1), 1, f);
fclose(f);
Что же тут неправильного? Ну что же, для этого придется немного пофилософствовать. Как бы много не говорили о том, что C --- это почти то же самое, что и ассемблер, не надо забывать, что он является все-таки языком высокого уровня. Следовательно, в принципе, программа написанная на C (или C++) может (теоретически) компилироваться на разных компиляторах и разных плафтормах. К чему это? К тому, что данные, которые сохранены подобным образом, в принципе не переносимы.

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

Плохо это тем, что при переносе данных на другую машину при попытке прочитать их той же программой (или программой, использующую те же структуры) вполне можно ожидать несколько некорректных результатов. Это связано с тем, что структуры могут быть представлены по другому в памяти (другое выравнивание), различается порядок следования байтов в слове и т.п. Как этого избежать?

Обычный "костыль", который применяется, например, при проблемах с выравниванием, заключается в том, что компилятору явно указывается как надо расставлять поля в структурах. В принципе, любой компилятор дает возможность управлять выравниванием. Но выставить одно значение для всего проекта при помощи ключей компилятора (обычно это значение равно 1, потому что при этом в сохраненном файле не будет пустых мест) нехорошо, потому что это может снизить скорость выполнения программы. Есть еще один способ указания компилятору размера выравнивания, он заключается в использовании директивы препроцессора #pragma. Это не оговорено стандартом, но обычно есть директива #pragma pack, позволяющая сменить выравнивание для определенного отрезка исходного текста. Выглядит это обычно примерно так:

#pragma pack(1)

struct { /* ... */ };

#pragma pack(4)
Последняя директива #pragma pack(4) служит для того, что бы вернуться к более раннему значению выравнивания. В принципе, конечно же при написании исходного текста никогда доподлинно заранее неизвестно, какое же было значение выравнивания до его смены, поэтому в некоторых компиляторах под Win32 есть возможность использования стека значений (пошло это, насколько я понимаю, из MS Visual C++):

#pragma pack(push, 1)

struct { /* ... */ };

#pragma pack(pop)
В примере выше сначала сохраняется текущее значение выравнивания, затем оно заменяется 1, затем восстанавливается ранее сохраненное значение. При этом, подобный синтаксис поддерживает даже gcc для win32 (еще стоит заметить, что, вроде, он же под Unix использовать такую запись #pragma pack не дает). Есть альтернативная форма #pragma pack(), поддерживаемая многими компилятороами (включая msvc и gcc), которая устанавливает значение выравнивания по-умолчанию.

И, тем не менее, это не хорошо. Опять же, это дает очень интересные ошибки. Представим себе следующую организацию исходного текста. Сначала заголовочный файл inc.h:

#ifndef __inc_h__
#define __inc_h__

class Object
{
// ...
};

#endif // __inc_h__
Представьте себе, что существуют три файла file1.cpp, file2.cpp и file2.h, которые этот хидер используют. Допустим, что в file2.h находится функция foo, которая (например) записывает Object в файл:

// file1.cpp
#include "inc.h"
#include "file2.h"

int main()
{
Object* obj = new Object();

foo(obj, "file");

delete obj;

return 0;
}
// file2.h
#ifndef __file2_h__
#define __file2_h__

#pragma pack(1)

#include "inc.h"

void foo(const Object* obj, const char* fname);

#pragma pack(4)

#endif // __file2_h__
// file2.cpp
#include "file2.h"

void foo(const Object* obj, const char* fname)
{
// ...
}
Это все скомпилируется, но работать не будет. Почему? Потому что в двух разных единицах компиляции (file1.cpp и file2.cpp) используется разное выравнивание для одних и тех же структур данных (в данном случае, для объектов класса Object). Это даст то, что объект переданный по указателю в функцию foo() из функции main() будет разным (и, конечно же, совсем неправдоподобным). Понятно, что это явный пример "плохой" организации исходных текстов --- использование директив компилятора при включении заголовочных файлов, но, поверьте, он не высосан из пальца. Мне такое несколько раз попадалось.

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

К чему я веду: на самом деле #pragma pack не является панацеей. Мало того, использование этой директивы практически всегда неправомерно. Я даже могу сказать более: эта директива в принципе редко когда нужна (во всяком случае, при прикладном программировании).

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

template<class T>
inline size_t get_size(const T& obj)
{
return sizeof(obj);
}
Эта функция возвращает размер, необходимый для записи объекта. Зачем она понадобилась? Во-первых, возможен вариант, что sizeof возвращает размер не в байтах, а в каких-то собственных единицах. Во-вторых, и это значительно более необходимо, объекты, для которых вычисляется размер, могут быть не настолько простыми, как int. Например:

template<>
inline size_t get_size<std::string>(const std::string& s)
{
return s.length() + 1;
}
Надеюсь, понятно, почему выше нельзя было использовать sizeof.

Аналогичным образом определяются функции, сохраняющие в буфер данные и извлекающие из буфера информацию:

typedef unsigned char byte_t;

template<class T>
inline size_t save(const T& i, byte_t* buf)
{
*((T*)buf) = i;
return get_size(i);
}

template<class T>
inline size_t restore(T& i, const byte_t* buf)
{
i = *((T*)buf);
return get_size(i);
}
Понятно, что это работает только для простых типов (int или float), уж очень много чего наворочено: явное приведение указателя к другому типу, оператор присваивания... конечно же, очень нехорошо, что такой save() доступен для всех объектов. Понятно, что очень просто от него избавиться убрав шаблонность функции и реализовав аналогичный save() для каждого из простых типов данных. Тем не менее, это всего-лишь примеры использования, не судите строго --- я писал их параллельно с этим текстом.

template<>
inline size_t save<MyObject>(const MyObject& s, byte_t* buf)
{
// ...
}
Не спорю, можно сделать и по другому. Например, ввести методы save() и restore() в каждый из сохраняемых классов, но это не столь важно для принципа этой схемы. Поверьте, это достаточно просто использовать, надо только попробовать. Мало того, здесь можно вставить в save<long>() вызов htonl() и в restore<long>() вызов ntohl(), после чего сразу же упрощяется перенос двоичных файлов на плафтормы с другим порядком байтов в слове... в общем, преимуществ --- море. Перечислять все из них не стоит, но как после этого лучше выглядит исходный текст ;) а как приятно вносить изменения ;)

Резюме

Работа с бинарными файлами далеко не так проста как кажется. Немного невнимательности, и работа программы уже не так предсказуема, как ранее.

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#20 dyvniy » Пт, 9 сентября 2016, 00:40:39

Использование pthreads
http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html

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

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *print_message_functionvoid *ptr );

main()
{
     
pthread_t thread1thread2;
     const 
char *message1 "Thread 1";
     const 
char *message2 "Thread 2";
     
int  iret1iret2;

    
/* Create independent threads each of which will execute function */

     
iret1 pthread_create( &thread1NULLprint_message_function, (void*) message1);
     if(
iret1)
     {
         
fprintf(stderr,"Error - pthread_create() return code: %d\n",iret1);
         exit(
EXIT_FAILURE);
     }

     
iret2 pthread_create( &thread2NULLprint_message_function, (void*) message2);
     if(
iret2)
     {
         
fprintf(stderr,"Error - pthread_create() return code: %d\n",iret2);
         exit(
EXIT_FAILURE);
     }

     
printf("pthread_create() for thread 1 returns: %d\n",iret1);
     
printf("pthread_create() for thread 2 returns: %d\n",iret2);

     
/* Wait till threads are complete before main continues. Unless we  */
     /* wait we run the risk of executing an exit which will terminate   */
     /* the process and all threads before the threads have completed.   */

     
pthread_jointhread1NULL);
     
pthread_jointhread2NULL); 

     exit(
EXIT_SUCCESS);
}

void *print_message_functionvoid *ptr )
{
     
char *message;
     
message = (char *) ptr;
     
printf("%s \n"message);
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#21 dyvniy » Чт, 6 октября 2016, 18:06:45

лямбды без захвата конвертируются в обычную функцию

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

DLGPROC callback =
    [] ( 
HWND hwndDlgUINT uMsgWPARAM wParamLPARAM lParam )->INT_PTR
  
{
    if ( 
uMsg == WM_CLOSE )
      return 
EndDialoghwndDlg);
    return 
false;
  };
 
  
INT_PTR err DialogBoxIndirectParamANULL, (LPCDLGTEMPLATE)hDialogTemplateNULLcallback);
 
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#22 dyvniy » Чт, 6 октября 2016, 18:18:00

Лямбда - это структура с переопределённым оператором скобок?
Обобщённые лямбда-функции C++14
В C++11 параметры лямбда-функций требовалось объявлять с указанием конкретных типов. C++14 снимает это ограничение и позволяет объявлять параметры лямбда-функций со спецификатором типа auto[7].

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

auto lambda = [](auto x, auto y) {return x + y;};

Выведение типов параметров обобщённых лямбда-функций выполняется по правилам, схожим с выведением типов для auto-переменных (но не является полностью идентичным). Приведённый выше код эквивалентен следующему[11]:

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

struct unnamed_lambda
{
 template<typename T, typename U>
 auto operator()(T x, U y) const {return x + y;}
};
auto lambda = unnamed_lambda();
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#23 dyvniy » Чт, 6 октября 2016, 18:19:16

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 5 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#24 dyvniy » Вт, 24 апреля 2018, 15:23:20

Рисование тэмплэйтами
http://www.eelis.net/C /analogliterals.xhtml
надо переписать на новый стандарт

С++ поддерживает утиную типизацию с помощью шаблонов.
Вложения
analogliterals.hpp
(5.21 КБ) 161 скачивание
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 10 гостей