Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Что такое rvalue и lvalue?

#cpp


На разных ресурсах нашел разные определения rvalue, lvalue. 
Как же правильно? 

right value или read value? 

left value или locator value? 
    


Ответы

Ответ 1



Исходно речь шла про правую и левую части относительно оператора присваивания. Но правильного варианта такой простой расшифровки нет и быть не может. Термины так и останутся rvalue и lvalue. А вот какой в них вложен смысл чётко прописано в стандарте. Всё это образует категории выражений. glvalue Выражение, чьё вычисление определяет сущность объекта, битового поля или функции. prvalue Выражение, чьё вычисление инициализирует объект, битовое поле или вычисляет значение операнда оператора, с соответствии с контекстом использования. xvalue Это glvalue, которое обозначает объект или битовое поле, чьи ресурсы могут быть повторно использованы (обычно потому, что они находятся около конца своего времени жизни). lvalue Это glvalue, которое не является xvalue. rvalue Это prvalue или xvalue. Таким образом любое выражение есть в первую очередь lvalue, xvalue или prvalue. rvalue - это уже обобщение.

Ответ 2



Не знаю, что Вы имеете ввиду под вопросом "как правильно". rvalue и lvalue - это категории выражений. Вот что написано в стандарте: — A glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function. — A prvalue is an expression > whose evaluation initializes an object or a bit-field, or computes the value of the operand of an operator, as specified by the context in which it appears. — An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime). — An lvalue is a glvalue that is not an xvalue. — An rvalue is a prvalue or an xvalue. //... [Note: Historically, lvalues and rvalues were so-called because they could appear on the left- and right-hand side of an assignment (although this is no longer generally true); glvalues are “generalized” lvalues, prvalues are “pure” rvalues, and xvalues are “eXpiring” lvalues. Despite their names, these terms classify expressions, not values. — end note]

Методы оптимизации, основанные на эффективном использовании оборудования

#алгоритм #оптимизация


А давайте соберём здесь неалгоритмические методы оптимизации программ. Не алгоритмы
«N*log n вместо N^2», а приёмы позволяющие использовать имеющееся оборудование более
эффективно. Что приходит на ум мне:

Использование кэша процессора:


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


Обход латентности доступа к данным (применимо и к чтению/записи данных процессором
из памяти и к работе с диском):


Чтение/запись данных последовательно, а не в случайном порядке. Пример: внешняя сортировка
позволяет обойтись эффективными операциями чтения/записи больших последовательных блоков
данных. Если реализовать quicksort с хранением данных на диске, то при объёме данных
> объёма кэша потребуется O(n * log n) движений головки диска. 
Буферизация ввода/вывода. Пример: всё та же внешняя сортировка. Одна из операций
там – слияние данных из нескольких файлов с диска и запись результата на диск. Это
будет работать гораздо быстрее, если читать данные из каждого входного файла порциями,
скажем, по 10 МБ и писать результаты в буфер, который сбрасывать на диск тоже по мере
накопления.
Группировка нужных данных. Пример: параллельные массивы. Допустим, у нас есть большой
массив точек на плоскости и описание к каждой из них длиной 50 символов. Тогда, например,
выбор точек, попадающих в некоторый прямоугольник (допустим, нам невыгодно применять
что-то более сложное, чем простой проход по массиву) будет работать существенно быстрее,
если разбить информацию на два массива:

struct THeader {  int x, y;  };
struct TInfo {  char description[50];  };
THeader dotHeaders[10000000], dotInfos[10000000];

Другой похожий пример (вопрос Примеры оптимизации путём группировки данных в памяти).


Оптимальное использование имеющейся пропускной способности:


Сжатие данных «на лету». Пример: объёмная база данных с некоторыми  закономерностями
в данных (между полями записей или между соседними записями). Сжатие данных перед записью
на диск и расжатие при чтении может позволить достичь большей скорости записи/чтения
с диска.
Использование специальных команд процессора, содержащих дополнительные "подсказки"
по работе с памятью. Пример: intrinsic'и _mm_stream_ps и т.п. в примере http://pastebin.com/Xzq9dYww
@superhackkiller1997'a. _mm_stream_ps компилируется в команду MOVNTPS - аналог memset,
работающий в обход кэша.


Обход «неудобных» шаблонов доступа к данным:


Выравнивание данных по границам, кратным 4, 8 и более байт. Обычно делается компилятором
именно потому, что на многих процессорах доступ, например, к 4-байтному целому в памяти
по адресу, не кратному 4, намного медленнее.


Использование нескольких имеющихся устройств одновременно (несколько ядер процессора/несколько
вычислительных устройств в ядре процессора/процессор и диск):


Многопоточная обработка. Эта тема слишком обширная чтобы пытаться охватить её здесь.
Использование специальных наборов команд процессора (MMX, SSE, …).
Расчёты на графических картах.
Обработка данных в одном потоке, буферизация и сохранение в другом потоке.


Обход латентности конвейера процессора:


Уменьшение количества условных переходов в принципе.
В частности – путём разворачивания циклов. Пример: для несложной обработки большого
массива данных 


вместо

for (int i = 0; i < n; i++) 
  process(a[i]);


может быть выгодно написать

int i, stopI = n - 4;
for (i = 0; i <= stopI; i += 4) {
  process(a[i]);
  process(a[i + 1]);
  process(a[i + 2]);
  process(a[i + 3]);
}
for ( ; i < n; i++) 
  process(a[i]);


(естественно, очень желательно, чтобы функции process была inline или просто одним
оператором)


Переупорядочивание операций. Подходит и для уменьшения зависимостей по данным и для
загрузки большего количества вычислительных устройств.
Совмещение итераций цикла. Аналогично предыдущему пункту – подходит для разных случаев.
Пример (тут тоже можно придумать что-нибудь получше): для обработки большого массива
данных 


вместо

for (int i = 0; i < n; i++) {
  b[i].x += deltaX;
  if (b[i].x < 0)
    b[i].x += 100;
  b[i].y *= b[i].x * y2;
}


-

int i, stopI = n - 2;
for (i = 0; i <= stopI; i += 2) {
  b[i].x += deltaX;
  if (b[i].x < 0)
    b[i].x += 100;
  b[i + 1].x += deltaX;   // Пока  b[i].y *= b[i].x * y2; ждёт выполнения процессором
условия и, возможно, += 100, мы начинаем обрабатывать следующий элемент
  if (b[i + 1].x < 0)
    b[i + 1].x += 100;
  b[i].y *= b[i].x * y2;
  b[i + 1].y *= b[i + 1].x * y2;
}
for ( ; i < n; i++) 
  …

    


Ответы

Ответ 1



Хорошая идея! Подождём ответов от специалистов по оптимизациям. Внесу свои 5 копеек: битовые трюки. Многие операции имеют неожиданно простую и эффективную реализацию с учётом особенностей двоичной записи чисел. Вот популярный сборник. Пример: для положительного числа v выражение (v & (v - 1)) == 0 определяет, является ли v степенью двойки. Ещё одним классическим трюком на грани между алгоритмическими и неалгоритмическими является быстрое вычисление 1/sqrt(x) из Quake.

Ответ 2



Внесу свои пять копеек. В многопоточных программах могут возникнуть проблемы с производительностью, когда потоки интенсивно выделяют/освобождают память. Это происходит из-за того, что стандартный malloc не умеет работать параллельно и просто делает lock на каждое выделение. С этим можно бороться по-разному, например выделяя большой кусок памяти для каждого потока, и используя его как пул. Но самый простой способ, это применить библиотеку tcmalloc, которая входит в gperttools. Самое приятное, что для ее использования ничего не надо менять в исходниках, нужно просто добавить флаг -ltcmalloc при линковке. На моей памяти простое добавление tcmalloc увеличивало производительность на 20-30% в одном проекте, связанном с алгоритмами на графе. UPD: Наваял демонстрацию: $ cat test.cc #include #include #include int main() { const int THREADS = 2; const int ITERATIONS = 50000000; std::vector threads; std::vector arr(THREADS, 0); for (int t = 0; t < THREADS; ++t) { threads.emplace_back([t, &arr]() { for (int i = 0; i < ITERATIONS; ++i) { std::unique_ptr ptr1(new int(i)); std::unique_ptr ptr2(new double(i)); arr[t] += *ptr1 + *ptr2; arr[t] %= ITERATIONS; } }); } for (std::thread& thread: threads) { thread.join(); } int sum = 0; for (int s: arr) { sum = (sum + s) % ITERATIONS; } return sum; } $ g++-4.7 --std=c++11 test.cc -O2 -pthread $ time ./a.out real 0m16.127s user 0m30.602s sys 0m0.172s $ g++-4.7 --std=c++11 test.cc -O2 -pthread -ltcmalloc $ time ./a.out real 0m8.743s user 0m16.417s sys 0m0.056s Как видно, даже на 2-х тредах tcmalloc ускорил программу почти в 2 раза.

Ответ 3



Вот такой примитвный. Просто транслирую с -DQUAKE и запускаю через time. avp@avp-xub11:~/src/codegoogle/smhasher$ grep CPU /proc/cpuinfo model name : Pentium(R) Dual-Core CPU E5400 @ 2.70GHz avp@avp-xub11:~/src/codegoogle/smhasher$ #include #include #include //float InvSqrt (float x); static inline float InvSqrt (float x){ float xhalf = 0.5f*x; int i = *(int*)&x; i = 0x5f3759df - (i>>1); x = *(float*)&i; x = x*(1.5f - xhalf*x*x); return x; } #if QUAKE #define WHO "QUAKE" #else #define WHO "sqrt() -lm " #endif int main() { int i; float x = 1.0; for (i = 0; i < 1000000000; i++) #if QUAKE x += InvSqrt(x); #else x += 1 / sqrt(x); #endif printf ("%s: x = %f\n", WHO, x); }

Ответ 4



К сожалению, у нас начинается флейм оптимизаторов-теоретиков и оптимизаторов-практиков (здесь и в вопросе-примере). Но т.к. я вспомнил практический пример в тему, я его всё-таки приведу :) . К вопросу о том, что всё, что не даёт лучшей оценки (O(n*log n) вместо O(n^2) и т.п.)- это баловство. Я тоже как-то, например, написал для каждой вставки в базу дополнительный select max(id)+1 ... Авто-инкремент там нельзя было сделать т.к. несколько процессов заливали данные в одну таблицу, но каждый - в своём диапазоне id, чтобы можно было по этим id в каждом диапазоне находить новые данные. Но суть не в этом. Суть в том, что когда код заработал, производительность была довольно грустной. Точно не помню, приведу очень примерные цифры ради отображения соотношений величин. Допустим, записывалось 50 элементов данных в секунду. При этом в процессе работы всех загрузчиков новых данных поступает 10 в секунду. Но когда загрузчики перезапускаются, им в сумме нужно загрузить 20000 элементов. Т.е. запас по прочности вроде как в 5 раз, но при рестарте надо ждать 6 минут. А код надо ещё тестировать и отлаживать. Было понятно, что select max(id)+1 это хрень, но избавление от него обещало прирост ну процентов 20 (что такого, быстренько в индексе пробежаться, тем более, он и так в памяти). И я точно помню, что мне казалось, что смысла нет. Тем не менее, после того, как мы немного подумали и я поправил эту и несколько подобных мелких проблем, производительность возросла раза в 3. И просто работать стало намного приятней! Не говоря о том, что я сэкономил себе много часов ожидания на имеющихся машинах (вполне нормальных по тем временам). Т.е. можно было ходить и плакаться, что мне надо машину на 1000$ дороже и сервер тоже, а можно было подумать-поработать пару дней. Upd: нашёл в одном вопросе (Книги по теме Concurrency и Parallel Programming) замечательный сборник статей. И в частности: приём переделки бинарных деревьев с тем, чтобы при спуске по дереву приходилось читать из памяти менее разбросанные данные: 1024CORES / RAM - не RAM, или Cache-Conscious Data Structures. Читается легко, всем рекомендую :)

Порядок уничтожения временных объектов

#cpp #g++ #language_lawyer


Недавно столкнулся с некоторой странностью при уничтожении временных объектов. Собственно,
вопрос следующий: почему объект под номером 2 удаляется раньше объекта под номером
1? Разве не должны выполняться деструкторы аргументов при выходе из области видимости,
то есть при возврате из функции? Почему вначале удаляется временный объект из функции
main, а затем аргумент foo? Почему не наоборот?

Код:

#include 
using std::clog;

struct A {
    static size_t counter;
    size_t me = counter++;
    A() { clog << "constructor A: " << me << "\n"; }
    A(const A& o) { clog << "copy A: " << me << "\n"; }
    A(A&& o) { clog << "move A: " << me << "\n"; }
    ~A() { clog << "destructor A: " << me << "\n"; }
};
size_t A::counter = 0;

A foo(A obj) {
    clog << "into foo\n";
    return obj;
}

int main() {
    A a;
    clog << "before foo\n";
    foo(a);
    clog << "between foo\n";
    A other = foo(a);
    clog << "after foo\n";
}


Вывод:

constructor A: 0
before foo
copy A: 1
into foo
move A: 2
destructor A: 2
destructor A: 1
between foo
copy A: 3
into foo
move A: 4
destructor A: 3
after foo
destructor A: 4
destructor A: 0


Компилятор GCC 5.1.1
    


Ответы

Ответ 1



Деструкторы вызываются в порядке обратном относительно вызывов конструкторов пл принципу стека LIFO (Last Input First Output). Сначало был создан объект с номером 1 посредством вызова конструктора копирования, так как исходный объект это lvalue . В стек также был занесен его деструктор. Затем внутри функции был вызван конструктор перемешения с номером 2 благодаря RVO (return value optimization). Этот конструктор вызван после конструктора параметра, а, следовательно, его деструктор помещен в стек перед деструктором объекта с номером 1. Таким образом последним помещенным в стек, объект с номером 2 удаляется первым. Согласно стандарту C++ (12.2 Temporary objects) 5....The destruction of a temporary whose lifetime is not extended by being bound to a reference is sequenced before the destruction of every temporary which is constructed earlier in the same full-expression. Переводя на русский язык, эта цитата из стандарта говорит о следующем: "Уничтожение временного объекта, чье время жизни не продлевается за счет привязки к ссылке, происходит перед уничтожением каждого временного объекта, который был создан ранее в том же самом полном выражении" Что касается второго предложения A other = foo(a); то временный объект строится непосредственно в объекте other, а потому и удаляется, когда сам этот объект удаляется. Если вы посмотрите на сообщения, которые сосответствуют этому предложению move A: 4 destructor A: 3 after foo destructor A: 4 то вы увидите, что благодаря RVO (return value optimization) локальный объект функции (ее параметр), который является lvalue, сразу же перемещается в объект other. На это указывает сообзения after foo destructor A: 4 То есть этот временный объект был построен в other и, соответственно удален после сообщения after foo когда функция main завершила свою работу.

Ответ 2



Почему у GCC такой порядок я честно говоря не знаю, у MSVC он другой - такой какой напрашивается. Но с моей точки зрения GCC имеет право на подобное поведение, т.к. стандарт говорит следующее: [5.2.2/4]: The lifetime of a parameter ends when the function in which it is defined returns. The initialization and destruction of each parameter occurs within the context of the calling function. Таким образом, параметр будет уничтожен уже после выполненного return в вызывающей функции. Но и возвращаемое значение будет уничтожено сразу по выходу из функции, т.к. выражение на этом заканчивается и все временные объекты из оного подлежат уничтожению. В принципе, возвращаемое значение "моложе" аргумента, а значит должно быть уничтожено до него. С другой стороны, деструктор возвращенного значения можно рассматривать как функцию, которая должна быть вызвана позже удаления аргумента. Таким образом можно считать, что стандарт несколько размыт в данном случае, а можно считать, что поведение GCC более соответствует стандарту. Предлагаю добавить в примеру всего пару строк, и всё станет ещё интереснее: #include using std::clog; struct A { static size_t counter; size_t me = counter++; int a; void foo() { clog << "foo me\n"; } A() { clog << "constructor A: " << me << "\n"; } A(const A& o) { clog << "copy A: " << me << "\n"; } A(A&& o) { clog << "move A: " << me << "\n"; } ~A() { clog << "destructor A: " << me << "\n"; } }; size_t A::counter = 0; A foo(A obj) { clog << "into foo\n"; return obj; } int main() { A a; clog << "before foo\n"; foo(a).foo(); clog << "between foo\n"; A other = foo(a); clog << "after foo\n"; } Вывод MSVC2013(Release): constructor A: 0 before foo copy A: 1 into foo move A: 2 destructor A: 1 foo me destructor A: 2 between foo copy A: 3 into foo move A: 4 destructor A: 3 after foo destructor A: 4 destructor A: 0 GCC/Clang: constructor A: 0 before foo copy A: 1 into foo move A: 2 foo me destructor A: 2 destructor A: 1 between foo copy A: 3 into foo move A: 4 destructor A: 3 after foo destructor A: 4 destructor A: 0 На мой взгляд логике стало ещё меньше и лично мне, вывод MSVC более логичен. Видимо разница вызвана тем, что для MSVC аргумент функции это объект локальный для функции, тогда как для GCC/Clang это объект принадлежащий full expression. А если совместить цитату из моего ответа, с цитатой из ответа Vlad from Moscow, тогда получается, что GCC/Clang всё делают правильно, а MSVC отходит от стандарта. Создал баг-репорт на connect, посмотрим, что ответят. Баг закрыли с формулировкой "by design" и привели ссылку, где указывается, что такое поведение допустимо. Таким образом мы имеем, что MSVC не содержит проблемы, но его поведение отличается от GCC/clang.

Ответ 3



Я подозреваю, что компилятор переписал функцию так: A foo(A obj) { clog << "into foo\n"; A result = std::move(obj); return result; } Clang, во всяком случае, выдает с ней такой же вывод. Тогда если забыть об RVO, то все выглядит логично. Но с RVO все равно у меня в голове не клеется. Скорее всего компилятор очень искусно притворяется, что не делает RVO.

Как программно узнать, что устройство работает в режиме Wi-Fi точки доступа?

#java #android #wifi


Как можно средствами Java узнать, что телефон находиться в режиме "точка доступа"?

Мне необходимо узнать, раздаёт ли телефон Wi-Fi / подключен ли к нему.

Вот код на проверку подключения к Wi-Fi:

public boolean isWiFiOn() {
    final WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
    if(wifi != null)
        switch(wifi.getWifiState()) {
            case(WifiManager.WIFI_STATE_DISABLED):
            case(WifiManager.WIFI_STATE_DISABLING):
                return false;
            default:
                return true;
        }
    return false;
}


Но этот код возвращает false, если телефон в режиме точки доступа.

Как в таком случае узнать, раздаёт ли он Wi-Fi?
    


Ответы

Ответ 1



Официально узнать нельзя, но Android на то и "андроид", что много чего можно сделать при помощи хаков: Есть спрятанный @hide метод getWifiApState: Method method = wifiManager.getClass().getDeclaredMethod("getWifiApState"); method.setAccessible(true); int actualState = (Integer) method.invoke(wifiManager, (Object[]) null); Далее actualState сравниваем с такими константами: public static int WIFI_AP_STATE_DISABLING = 10; //выключается public static int WIFI_AP_STATE_DISABLED = 11; //выключен public static int WIFI_AP_STATE_ENABLING = 12; //включается public static int WIFI_AP_STATE_ENABLED = 13; //включен public static int WIFI_AP_STATE_FAILED = 14; //сломалсо UPD: ссылка на метод в исходниках:

Что такое Unicode и как с ним связана UTF-8

#unicode


Со страницей википедии про Unicode ознакомился, но так и не понял, хотя там и написано
что это стандарт кодирования символов

Насколько я знаю то Unicode представлен следующим образом


  0x00000000 — 0x0010F800


Есть такое утверждение, что UTF-16 = Unicode, так ли это?

UTF-16 представлен как 256*256 = 65 536 (без суррогатных пар), с суррогатными парами
формула такая 2^20+2^16−2048 - тут не ясно как такая формула получилась, кто сведущ
объясните (без суррогатных пар все ясно)

UTF-8 представлен следующим образом


  4 байта (то что используется)
  
  0x00000000 — 0x001FFFFF
  
  6 байта (то что не используется)
  
  0x00000000 — 0x7FFFFFFF


Тут как бы назревает вопрос а как мы можем переводить из UTF-8 в Unicode если Unicode
кодирует в 2 раза меньше символов?

Что такое Unicode и зачем в него переводить допустим из той же самой UTF-8?

P.S Каша в голове, запутался уже X_X, помогите проявить ясность в голове
    


Ответы

Ответ 1



Резюмируя написанное в комментариях и чате. Примечание: данный ответ не претендует на строгую спецификацию, а является объяснением «на пальцах» для улучшения понимания и может содержать неточности, а для разработки программ лучше читать не SO и даже не Википедию, а непосредственно сам стандарт. В контексте вопроса Unicode — это просто табличка символов и закреплённых за ними целых чисел (не каких-либо байт, а обычных человеческих чисел). (А ещё юникод описывает обработку символов и их преобразования друг в друга, но вопрос не про это.) Кусочек этой таблички: Эта табличка не указывает, как именно эти числа переводить в байты, которые можно было бы сохранить в компьютере. Но, чтобы хранить юникод в компьютере, кто-то должен указать, как их всё-таки переводить! Возьмём числа из этой таблички символов и запихнём их в четыре байта так, чтобы четыре байта представляли беззнаковое целое число (unsigned int, uint32). То есть, например, из кода буквы «Я» 42F16 получаем байты 00 00 04 2F. Таким образом мы получили простейшую кодировку UTF-32 (UTF-32-BE1, UCS-4). Если интерпретировать эти полученные четыре байта как беззнаковое целое число, то мы получим номер символа в табличке Unicode. С уточнением, что мы используем uint32, можно для себя считать, что UTF-32 = Unicode: каждое число кодируется-декодируется как есть без каких-либо преобразований. Если мы возьмём числа из таблички и запихнём их в два байта, представляющие беззнаковое целое число (unsigned short, uint16), то мы получили бы первую версию Юникода, которая была в 1991 году. Тогда число символов было ограничено 65536, и все их коды можно было легко представить двумя байтами без дополнительных преобразований. Но потом решили, что 65 тысяч это как-то мало, и увеличили максимальное количество символов до миллиона. Но ведь миллион в два байта уже никак не запихнёшь, и нужно взять больше байт на символ. Но так как уже успели появиться программы, рассчитывающие на эти самые два байта (например, в Java или Windows NT длина одного символа до сих пор 2 байта), то для сохранения хоть какой-то обратной совместимости из этих двух байт выдрали диапазон D80016..DFFF16 и сказали, что это суррогатные пары, занимающие по четыре байта и обрабатывающиеся по особому алгоритму для символов с кодом 1000016 и больше. И назвали всё это кодировкой UTF-16. Таким образом, в UTF-16 числа из таблички юникода, попадающие в диапазоны 000016..D7FF16 и E00016..FFFF16, записываются в два байта как есть, а символы с кодами 1000016 и больше записываются в виде суррогатных пар с использованием диапазона D80016..DFFF16 и занимают четыре байта. Так старые приложения, сделанные под первую двухбайтовую версию Юникода, смогли продолжить и дальше с ним работать, если не используются суррогатные пары. Таким образом та же буква «Я» (код 42F16) представляется в UTF-16 (UTF-16-BE) как 04 2F, а символ «🔒» с кодом 1F51216, не влезающим в два байта, по алгоритму преобразуется в четыре байта D8 3D DD 12. Из-за этих самых суррогатных пар с кучей преобразований UTF-16 ≠ Unicode. UTF-8 была сделана для совместимости с ASCII: в ней коды символов от 0 до 7F16 записываются в один байт как есть, в итоге выдавая этот самый ASCII, а всё остальное кодируется по алгоритмам ещё более хитрым, чем в UTF-16, поэтому UTF-8 ≠ Unicode тоже. Технически UTF-8 позволяет закодировать числа аж до двух миллиардов, однако Unicode ограничен миллионом символов, и стандартом установлено, что кодировать в UTF-8 числа, не входящие в допустимый диапазон Unicode, запрещено. Поэтому проблемы перевода из UTF-8 в Unicode чисел, не входящих в Unicode, просто нет: хорошо сделанное приложение в таких случаях выкинет ошибку или заменит всё недопустимое символом �. В Notepad и Notepad++ можно наблюдать кодировку, названную «Unicode», но на самом деле это UTF-16-LE с BOM1. (клик для увеличения) Самой распространённой кодировкой вроде как является UTF-8. 1 — числа, занимающие более одного байта, можно записывать с разным порядком байт: например, число 259 можно записать как 01 03 (big endian, BE) или как 03 01 (little endian, LE). То же самое применимо к символам в кодировках UTF-16, UTF-32 и некоторым другим (но не UTF-8). Вышеупомянутый 🔒 кодируется в UTF-16-LE неё как 3D D8 12 DD — в то время как в UTF-16-BE это будет D8 3D DD 12. BOM — это специальный символ FEFF16 в начале текста, который позволяет определить этот самый порядок байт — в big endian он пишется как FE FF, а в little endian как FF FE, а использование символа FFFE16 запрещено, что и позволяет определить порядок байт.

Является ли функция функтором

#cpp


Является ли функция функтором? 

Функцию можно применять в качестве функтора в STL алгоритмах, но почему-то книги
упорно доказывают, что функция это функция, а функтор - это именно объект
    


Ответы

Ответ 1



В стандарте C++ нет такого термина, как функтор. Обычно в книгах под этим понятием подразумевают так называемый объект функции - термин, который действительно определен в стандарте C++. Из стандарта C++ (20.9 Function objects) 1 A function object type is an object type (3.9) that can be the type of the postfix-expression in a function call (5.2.2, 13.3.1.1).230 A function object is an object of a function object type. In the places where one would expect to pass a pointer to a function to an algorithmic template (Clause 25), the interface is specified to accept a function object. This not only makes algorithmic templates work with pointers to functions, but also enables them to work with arbitrary function objects. Поэтому под функторами авторы книг скорей всего имеют в виду объекты функций. Так как функции не относятся к типам объектов, то они не включаются в это понятие. С другой стороны а сноске 230 написано относительно типа постфиксного выражения вызова функции 230) Such a type is a function pointer or a class type which has a member operator() or a class type which has a conversion to a pointer to function. Тем не менее, алгоритмы могут принимать функции по ссылке, а не обязательно указатели на функции. Сама приведенная цитата может трактоваться неоднозначно. Думаю, что ключом к ее правильной трактовке является следующая фраза из приведенной цитвты In the places where one would expect to pass a pointer to a function to an algorithmic template (Clause 25), the interface is specified to accept a function object. Именно здесь, вероятно, авторы книг проводят водораздел между объектами, которые предоставляют оператор функцию, и простыми указателями на функции, называя первых функторами.

Ответ 2



Основное отличие функции от функтора заключается в том, что функция не имеет состояния, а функтор, являющийся объектом, обладать состоянием может. Конечно, можно предложить к рассмотрению частный случай, когда внутри функции определена статическая переменная, или используется какая-то внешняя (глобальная) переменная. Но такие функции нельзя будет использовать в двух разных контекстах, т.к. состояние будет общее. Использование функтора (т.е. объекта, с перегруженным operator()) позволяет разделять состояние для разных вызовов. Так же типы функторов могут использоваться в качестве параметров шаблона. Разные типы функторов позволят получать разные типы инстанцированных объектов, даже если сигнатуры operator() у них одинаковые. А если у Вас есть две разные функции с одинаковыми сигнатурами, например, int g(double), int f(double), то тип этих функций всё равно будет одинаковый int(double).

Ответ 3



Да запросто. Просто это - вызываемый объект. Функция - вызываема? несомненно. Объект? А почему нет? Что такое указатель, как не объект определенного типа? Короче, функция - это частный случай функтора, только и всего.

Как передать данные между экранами в Чистой архитектуре?

#android #архитектура


Исходные классы: 
EventListPresenter и EventFiltersPresenter - презентеры, EventRepository - репозиторий,
EventFilters - java класс, который содержит поля фильтров.

Задача:

EventListPresenter должен уметь отображать Event по фильтрам. Пользователь настраивает
фильтры на отдельном экране. После настройки фильтров нажимает кнопку "Применить" и
переходит обратно на экран со списком, но уже должны примениться новые фильтры. Как
это правильно сделать в Чистой архитектуре? 

Слышал, что делать это через ActivityOnResult  - плохая практика, можете объяснить
почему и как правильно, если можно с примером (псевдокод)? 
    


Ответы

Ответ 1



Чистая архитектура говорит только о зависимостях между слоями приложения (бизнес логика ничего не знает о внешнем представлении и так далее). Понятие "экранов" в приложении относится к презентейшн слою, который можно реализовать различными MV* паттернами. Например MVP. Самое удобное и правильное для передачи событий между экранами (то есть между их презентерами) - создать общую модельку CurrentFilter с текущим фильтром в памяти. Тогда EventListPresenter подпишется на обновления этой модельки и будет реагировать на изменения фильтра, то есть запрашивать новые данные из репозитория. А EventFiltersPresenter будет менять фильтр внутри этой модельки. Есть множество способов это реализовать, но наиболее распространенным является использование CustomScoup Dagger 2. То есть первый презентер EventListPresenter при инициализации создаст CurrentFilter, а при уничтожении очистит. Так как второй экран запускается заведомо после EventListPresenter, то для него CurrentFilter уже будет доступен. Использование onActivityResult допустимо для запуска внешних приложений типа камеры, тогда результат полученный в Intent передается презентеру и дальше уже обрабатывается там. В других случаях это менее удобно, так как передавать можно только серелизуемые данные и в ответственность Активити попадает еще и логика передачи данных кроме отображения.

Ответ 2



Если вы хотите следовать принципам "чистой архитектуры", под которой имеется ввиду строгое распределение на слои, каждый из которых отвечает только за свой конкретный отрезок работы (Single Responsibility), то использоваеonActivityResult нарушает как раз нарушает принцип единой ответственности слоя. Рассмотрим распостраненный вариант архитектуры: View -> Presenter -> Repository -> Model Каждый знает только о следующем и/или предыдущем слое. То есть Model ничего не знает о Presenter и View. На всякий случай уточню, что View это зачастую Activity или Fragment. И тут непресредственно ответ на Ваш последний вопрос: onActivityResult вернет Вашу модель во View (который ответственный за вызов новых экранов), то есть в слой, который как бы за модель не ответственный. Если придерживаться такой архитектуре, то используется interactor. Обычно он представляет собой интерфейс, с одним методом interaction(), который выполняет задачу и передает данные/модели средствами Event bus, который "слушает" нужные события в нужных местах. Своим ответом я не заявляю о правильности использования только такого подхода. Такие вопросы всегда являются повод демагогий и бесконечных обсуждений, но в итоге все соглашаются, что к правильной (значит гибкой, расширяемой и взаимозаменяемой) архитектуре надо стремиться, но единственного правильного варианта нет, а каждый выбирает что ему удобнее. Рекомендую ознакомиться с архитектурным гайдом от гугла, который был представлен на Google I/O 2017.