Страницы

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

среда, 3 октября 2018 г.

Контрактное программирование Code Contracts [закрыт]

Насколько активно стоит использовать контрактное программирование в проектах и как правильно?
Столкнулся первый раз и не могу понять насколько полезная штука, с первого взгляда кажется очень заманчивым.
P.S. Желательно применительно к C#.


Ответ

Уже дали в комментах ссылку на мой пост "Начать ли использовать контракты", но, ИМХО, есть смысл дать краткую выжимку.
Дизайн - это дело сложное. Обычно сложность заключается в том, что не понятно, что каждый класс/модуль должен делать, может делать и как их правильно использовать: какие "правильные" входные значения, в каком порядке нужно дергать методы и т.п. Другими словами, очень сложно глядя на абстрактный код в вакууме сказать, каким образом правильно пользоваться этим классом или, если речь идет о самом процессе проектирования, как "правильно" разбить обязанности между классами, чтобы система не развалилась под собственной тяжестью. Сходу сложно сказать, является ли текущее поведение фичей, багом, или просто не подумали.
Контракты сами по себе - вещь относительно простая, - это способ задания ожидаемого поведения (еще это назвают спецификацией) в виде утверждений (assertions). Например, метод может "потребовать" у вызывающего кода валидную строку на вход, после чего он "обязуется" вернуть ее размер. Если же входное условие не будет выполнено, то никаких гарантий по результату не будет.
Фундаментальная идея, которая лежит в основе контрактов, помогает отсечь из безграничного списка возможных состояний лишь те, которые интересны в данном контексте (читай, для данной задачи). Почему это важно? Да просто потому, что сложность растет очень быстро и без каких-то ухищрений забодает даже самую светлую голову.
Вот пример, который я обычно привожу (он несколько абстрактен, к сожалению).
Допустим, у нас есть класс с тремя булевыми полями: b1, b2, b3. Каково число возможных состояний объекта этого класса? Правильно, 2 ^ 3, т.е. 8. А что если там мы добавим еще 2 булевых поля? То общее число состояний увеличится до 32.
Причем здесь контракты? Допустим, что в нашем объекте с 5 булевыми полями не все комбинации возможны. И с помощью предусловий конструктора, мы можем ограничить число возможных состояний лишь определенным понятным всем подмножеством. Вот надуманный пример:
class StupidClass { public StupidClass(bool b1, bool b2, bool b3) { // Утверждаем, что если b1 == true, то остальные два значения должны быть false. // Иначе, утверждаем, что b2 или b3 могут быть true. } }
Пример дурацкий и не самый удачный, но он показывает, как можно с помощью валидации аргументов конструктора "сузить" пространство решений с 8 возможных комбинаций до, например, 2-х или трех. Это называется умным понятием инварианта, что имеет полезные практические последствия в виде уменьшения числа возможных "а что если".
Все это может показаться не важным, когда задача у вас в голове и все вот эти допущения для вас совершенно очевидны. Но, поверьте, они перестанут быть очевидными через пару месяцев даже для вас, и точно будут совершенно не очевидными для постороннего мозга.
Теперь, если я, вдруг, заинтересовал этой темой, то готов поделиться дополнительными ссылками для изучения этого дела. Я, наверное, один из самых больших поклонников контрактного программирования в рунете, что вылилось в десятки статей/сообщений и в участие в проекте Code Contracts
Вот вмнеяемый набор ссылок.
Базовые вещи, чтобы понять, что это такое и нужно ли это.
Начать ли использовать контракты? - уже было. Повторенье, ученье, мать его:) Как не надо писать код - Тут как раз я сравниваю на практике, в чем выгоды от использования проектирования по контракту для борьбы со сложностью.
Более академические статьи
Проектирование по контракту. Корректность ПО Проектирование по контракту. Утверждения Утверждения и защитное программирование Проектирование по контракту. Наследование Мониторинг утверждений в период выполнения Контракты, состояние и юнит-тесты
Специфичное для Code Contracts
Наследование интерфейсов и контракты Предусловия в конструкторах наследников Когда предусловия не являются предусловиями

Зависает оператор `await` в оконном приложении / программа висит при вызове Task.Result или Wait

Есть простой код
private static void Foo() { Bar().Wait(); Console.WriteLine("Foo() done."); }
private static async Task Bar() { await Task.Delay(1000); Console.WriteLine("Bar() done."); }
Почему при вызове Foo() программа зависает и на консоль ничего не выводится?
Как этот код исправить?


Ответ

Объяснение
Такое случается при работе в потоке UI. Дело в том, что все асинхронные вызовы, сделанные из потока UI, после выполнения "возвращаются" обратно в свой поток. И если этот поток заблокирован ожиданием окончания вызова - привет взаимоблокировка!
Разберем что происходит подробнее.
Вызывается метод Bar Начинается задача Task.Delay(1000) Управление из метода Bar возвращается в метод Foo В методе Foo начинается синхронное ожидание результата задачи, которое останавливает очередь сообщений. Через секунду завершается задача Task.Delay(1000) в потоке таймера Потоку UI посылается оконное сообщение, чтобы он возобновил выполнение метода Bar
Но поток UI не может обработать это сообщение - ведь он висит в методе Foo! Все. Взаимоблокировка.
Решение первое - сквозная асинхронность.
Все просто - если вызов Wait() вешает программу - надо избежать его. К примеру, сделать функцию Foo асинхронной:
private static async Task Foo() { await Bar(); Console.WriteLine("Foo() done."); }
private static async Task Bar() { await Task.Delay(1000); Console.WriteLine("Bar() done."); }
Разумеется, одно такое преобразование проблему не решает - ведь кто-то же вызывает Foo() и ему теперь тоже надо дождаться окончания ее работы. Поэтому это преобразование придется делать уровень за уровнем до самого верха.
Закончится все, скорее всего, обработчиком событий. Его придется сделать async void-методом. Если Foo - обработчик события, то это будет выглядеть так:
private static async void Foo() { await Bar(); Console.WriteLine("Foo() done."); }
Но у таких методов есть проблема. Если вылетит исключение - оно либо уронит программу, либо вы про него никогда не узнаете. Поэтому всегда настраивайте обработчики неперехваченных исключений.
Для WinForms это делается так:
Application.ThreadException += Application_ThreadException;
// ...
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { // Тут надо записать исключение в лог или показать MessageBox }
Решение второе - уйти в пул потоков
Если вывести исполнение метода Bar из потока UI - проблема также исчезнет. Для этого достаточно запустить Bar в пуле потоков любым доступным способом.
К примеру, вот так:
private static void Foo() { Func f = Bar; f.EndInvoke(f.BeginInvoke(null, null)).Wait(); Console.WriteLine("Foo() done."); }
Но этот код я привел только для примера. EndInvoke/BeginInvoke попросту выглядит некрасиво - так что лучше использовать более свежее API.
Наверное, наиболее красивый способ - вот этот:
private static void Foo() { Task.Run(() => Bar()).Wait(); Console.WriteLine("Foo() done."); }
Однако, при использовании Task.Run не удастся обойтись без замыкания, если Bar принимает хотя бы один параметр. Получается лишняя функция в цепочке вызовов. В этом нет ничего страшного для уже существующего кода - но при написании кода с нуля может возникнуть желание писать без дополнительных функций.
Нет ничего проще:
private static void Foo() { Task.Run(async () => { await Task.Delay(1000); Console.WriteLine("Foo() inner task done."); }).Wait(); Console.WriteLine("Foo() done."); }
Решение третье - "побег из потока UI"
Оба прошлых решения были основаны на изменении кода Foo - но это будет менять код Bar. В этом есть некоторый смысл как в плане защитного программирования, так и в плане оптимизации. Дело в том, что асинхронный код быстрее выполняется в пуле потоков, чем в потоке UI - а потому большие куски кода, которые не работают с UI-контролами, из потока UI целесообразно вытаскивать.
Это можно было бы сделать обернув весь метод Bar в одну большую лябмду и воспользовавшиcь Task.Run - но красивым такой способ не назвать. Поэтому "побег из потока UI" чаще всего делают при помощи "хитрых" форм оператора await
Самый простой вариант - это вот такой:
private static async Task Bar() { await Task.Delay(1000).ConfigureAwait(false); Console.WriteLine("Bar() done."); }
Вызов ConfigureAwait(false) говорит продолжать исполнение в том потоке, где выполнилась задача, не возвращаясь обратно в поток UI. И такой код работает.
Но у ConfigureAwait(false) есть свои подводные камни. Во-первых, если задача была выполнена еще до вызова await - то реального переключения не произойдет! А значит, вот такой код работать не будет:
private static async Task Bar() { await Task.FromResult(0).ConfigureAwait(false); // не работает await Task.Delay(1000); Console.WriteLine("Bar() done."); }
Поэтому, ConfigureAwait(false) желательно дописывать к каждому вызову await, а не только к первому. Это значительно снижает визуальную красоту решения.
Во-вторых, вложенные асинхронные вызовы не защищены от взаимоблокировок, как это было с Task.Run! Особенно первый вложенный вызов, ведь он делается еще до переключения на поток пула.
private static async Task Bar() { await Baz().ConfigureAwait(false); Console.WriteLine("Bar() done."); }
private static async Task Baz() { await Task.Delay(1000); // тут все равно повисло }
Кроме того, код выше содержит и другую проблему. Задача Baz() выполнялась в потоке UI - а значит, вызов await Baz().ConfigureAwait(false); переключит нас ... на поток UI, а вовсе не на поток пула, куда мы так стремились!
Так что серебряной пулей ConfigureAwait(false) определенно не является.
Если есть желание окончательно переключаться в поток пула одной строчкой, могу предложить вот такой хелпер:
struct ContextSwitcher : INotifyCompletion { private SynchronizationContext target;
public static ContextSwitcher SwitchToBackground() { return SwitchToContext(null); }
public static ContextSwitcher SwitchToContext(SynchronizationContext target) { return new ContextSwitcher { target = target }; }
public ContextSwitcher GetAwaiter() { return this; }
public bool IsCompleted { get { return SynchronizationContext.Current == target; } }
public void GetResult() { }
public void OnCompleted(Action continuation) { if (target == null) continuation.BeginInvoke(continuation.EndInvoke, null); else target.Post(_ => continuation(), null); } }
Использование:
private static async Task Bar() { await ContextSwitcher.SwitchToBackground();
await Task.Delay(1000); // Мы уже в потоке пула, и у нас нет никаких взаимоблокировок Console.WriteLine("Bar() done."); }
Решение четвертое - ожидание в своем контексте синхронизации
Зачем создавать отдельный поток - если все асинхронные продолжения можно выполнить в текущем? Надо лишь придумать как выполнять их во время ожидания...
К примеру, для этого можно установить свой контекст синхронизации, который будет ставить продолжения в очередь - а во время ожидания он будет из этой очереди продолжения исполнять.
Тут самое сложное - аккуратно обработать ситуацию поступления продолжения в очередь когда ожидание закончилось. Такое продолжение надо вернуть родительскому контексту...
class QueueSynchronizationContext : SynchronizationContext, IDisposable { struct PostData { public SendOrPostCallback d; public object state; }
private BlockingCollection queue = new BlockingCollection(new ConcurrentQueue()); private readonly SynchronizationContext parent = SynchronizationContext.Current;
public QueueSynchronizationContext() { SynchronizationContext.SetSynchronizationContext(this); }
public override void Send(SendOrPostCallback d, object state) { throw new NotSupportedException(); }
public override void Post(SendOrPostCallback d, object state) { var q = queue; try { if (q != null) q.Add(new PostData { d = d, state = state }); } catch (InvalidOperationException) { // Мы можем сюда попасть после вызова CompleteAdding, если он произошел только что (гонка) или если измененная queue еще не видна в текущем потоке // ObjectDisposedException попадает сюда же q = null; } if (q == null) PostToParent(d, state); }
private void PostToParent(SendOrPostCallback d, object state) { if (parent == null) d.BeginInvoke(state, d.EndInvoke, null); else parent.Post(d, state); }
public void Dispose() { using (var queue_local = Interlocked.Exchange(ref queue, null)) if (queue_local != null) { queue_local.CompleteAdding();
foreach (var data in queue_local.GetConsumingEnumerable()) PostToParent(data.d, data.state); }
SynchronizationContext.SetSynchronizationContext(parent); }
public void RunLoop(CancellationToken token) { var queue_local = queue; if (queue_local == null) throw new ObjectDisposedException("QueueSynchronizationContext");
try { foreach (var data in queue_local.GetConsumingEnumerable(token)) data.d(data.state); } catch (OperationCanceledException) { // Это нормальный выход из queue_local.GetConsumingEnumerable(token) при отмене токена return; }
// А если мы добрались сюда - значит, кто-то нас уже закрыл throw new ObjectDisposedException("QueueSynchronizationContext"); }
public void WaitFor(Task task, int timeout = Timeout.Infinite) { using (var source = new CancellationTokenSource(timeout)) { task.ContinueWith(_ => source.Cancel(), TaskContinuationOptions.ExecuteSynchronously); RunLoop(source.Token); } }
public void WaitFor(Task task, TimeSpan timeout) { using (var source = new CancellationTokenSource(timeout)) { task.ContinueWith(_ => source.Cancel(), TaskContinuationOptions.ExecuteSynchronously); RunLoop(source.Token); } }
public void WaitFor(Task task, CancellationToken token) { using (var source = CancellationTokenSource.CreateLinkedTokenSource(token)) { task.ContinueWith(_ => source.Cancel(), TaskContinuationOptions.ExecuteSynchronously); RunLoop(source.Token); } } }
Использование:
private static void Foo() { using (var ctx = new QueueSynchronizationContext()) { ctx.WaitFor(Bar()); Console.WriteLine("Foo() done."); } }
Замечание - внутри этого блока using нельзя использовать оператор await по понятным причинам. А вот внутри вызываемых методов (например, внутри Bar) - можно.
Кстати, нечто подобное использует Windows Workflow Foundation для "синхронного" исполнения рабочего процесса. Только там получается проще из-за особенностей работы (максимум 1 продолжение в очереди и никаких продолжений после завершения не остается гарантировано).

Как в C/C++ узнать, является ли тип знаковым или беззнаковым?

Сталкиваясь с типами данных, подобными time_t, size_t и другими, очевидно "численными" (считая, что указатель это тоже число) типами, иногда становится просто интересно, а это signed или unsigned тип?
Как, не роясь во всех include-файлах, где находятся их определения, часто спрятанные в дебрях условных макроподстановок, выяснить это?
По крайней мере для x86_64 GNU/Linux и компилятора gcc/g++ можно использовать простой прием, заключающийся в вычитании единицы из нуля и проверки, меньше ли нуля результат вычитания. Если меньше, то мы имеем signed тип, а иначе он unsigned
Такой прием можно оформить парой макросов:
#define SIGNED_TYPE(typename) ({volatile typename v = 0; volatile typename v1 = v - 1; v > v1;})
определяет знаковость типа по имени типа, а макрос
#define SIGNED_VAR(var) ({volatile typeof(var) v = 0, v1 = v - 1; v > v1;})
по переменной данного типа.
И если с первым макро все хорошо, то второй работает только при компиляции с флагом -std=... , определяющим GNU расширения C/C++ (в т.ч. без флага -std=..., т.е. "по умолчанию" для gcc/g++), а вот для -std=c89, -std=c11 и т.д. не компилируется, поскольку typeof() это GNU расширение.
Такая модификация макроса для определения "знаковости" переменной
#ifndef __STRICT_ANSI__ // GNU extensions #define SIGNED_VAR(var) ({volatile typeof(var) v = 0, v1 = v - 1; v > v1;}) #else #define SIGNED_VAR(var) ({var = 0; var > var - 1;}) #endif
конечно ужасна, во-первых, макрос меняет значение тестируемой переменной, а во-вторых (и это главное), неправильно определяет "знаковость" указателя (правильно -- unsigned) при компиляции в __STRICT_ANSI__ с оптимизацией выше -O1
Маленькая программа для иллюстрации.
// compile gcc/g++ #include #include #include
int main (int ac, char *av[]) { time_t t = 0, t1 = t - 1; size_t s = 0, s1 = s - 1; char *p = 0, *p1 = p - 1; int i = 0, i1 = i - 1, *pp = 0, *pp1 = pp - 1; double d = 0, d1 = d - 1;
printf("time_t: %ssigned
", t1 < t ? "" : "un"); printf("size_t: %ssigned
", s1 < s ? "" : "un"); printf("int: %ssigned
", i1 < i ? "" : "un"); printf("double: %ssigned
", d1 < d ? "" : "un"); printf("char *: %ssigned
", p1 < p ? "" : "un"); printf(" demo for pointers
%p > %p %s
", pp, pp1, pp > pp1 ? "true" : "false"); puts("
check macro");
#define SIGNED_TYPE(typename) ({volatile typename v = 0; volatile typename v1 = v - 1; v > v1;})
printf("SIGNED_TYPE(time_t): %ssigned
", SIGNED_TYPE(time_t) ? "" : "un"); printf("SIGNED_TYPE(size_t): %ssigned
", SIGNED_TYPE(size_t) ? "" : "un"); printf("SIGNED_TYPE(char *): %ssigned
", SIGNED_TYPE(char *) ? "" : "un"); printf("SIGNED_TYPE(int): %ssigned
", SIGNED_TYPE(int) ? "" : "un"); printf("SIGNED_TYPE(double): %ssigned
", SIGNED_TYPE(double) ? "" : "un"); printf("SIGNED_TYPE(long long): %ssigned
", SIGNED_TYPE(long long) ? "" : "un"); printf("SIGNED_TYPE(unsigned char): %ssigned
", SIGNED_TYPE(unsigned char) ? "" : "un");
#ifndef __STRICT_ANSI__ // GNU extensions #define SIGNED_VAR(var) ({volatile typeof(var) v = 0, v1 = v - 1; v > v1;}) #else // UGLY, because change var, WRONG for -std=c++... -std=c... with -Ox #define SIGNED_VAR(var) ({var = 0; var > var - 1;}) #endif
printf("SIGNED_VAR(time_t t): %ssigned
", SIGNED_VAR(t) ? "" : "un"); printf("SIGNED_VAR(size_t s): %ssigned
", SIGNED_VAR(s) ? "" : "un"); printf("SIGNED_VAR(int i): %ssigned
", SIGNED_VAR(i) ? "" : "un"); printf("SIGNED_VAR(double d): %ssigned
", SIGNED_VAR(d) ? "" : "un"); printf("SIGNED_VAR(char *p): %ssigned
", SIGNED_VAR(p) ? "" : "un");
return puts("End") == EOF; }
Какими еще способами можно ответить на данный вопрос и работает ли описанный метод в системах, отличных от x86_64 GNU/Linux gcc/g++ ?
P.S. не стоит рассматривать эту задачку, как к имеющую смысл при практическом программировании, важную для использования API с такими типами, нет, к ней можно относиться только с т.з. удовлетворения естественного любопытства.


Ответ

В С++ для этого есть std::is_signed, std::is_unsigned
В Си можно написать макрос, который будет преобразовывать литерал 0 и выражение -1 к нужному типу:
#define IS_SIGNED_TYPE(type) ((type)0 > (type)-1)
Для переменных можно было бы использовать var^var чтобы получить 0 нужного типа. Но это работает только для целых чисел. Так же нам надо получить (type)-1 что невозможно если использовать только арифметические операции, например тип var^var - 1 - это int По этому надо использовать такое расширение компилятора, например __typeof__
#define IS_SIGNED_VAR(var) (IS_SIGNED_TYPE(__typeof__(var)))

Почему ++i считается lvalue, а i++ rvalue?

Почему ++i считается lvalue, а i++ rvalue? Я нашел ответ на данный вопрос на stackoverflow, но мой ужасный английский не позволяет мне грамотно в этом разобраться. Ведь приоритет префиксного и постфиксного ++ всё равно выше, чем & и по идее в любом случае будет сначала ++, а только потом & или я вообще не так понимаю?


Ответ

Потому что после выполнения выражений:
i = 0; (1) x = ++i; (2) x = i++; В x будут следующие значения: (1) x = 1; i = 1; (2) x = 0; i = 1;
Все эффекты связаны как раз с таким делением.
То есть ++i означает увеличить i на один и взять его для выражения, а i++ означает взять значение i для выражения и после увеличить i на 1

Зачем нужен pull request, если есть push?

На текущем проекте мне советовали загружать каждый таск так:
Создаем ветку по имени таска Комитим pull request - Отправляем все на удаленный репозиторий Далее мне советуют: "смотришь код и мерджишь"
Не совсем понятен четвертый пункт. Мне советуют для каждого таска ветвь создавать, заливать в нее изменения, и сливать обратно с master. Я понимаю, что репозитория два: локальный и удаленный.
Т.е. мне создавать на локальном ветку, коммитить, мержить и пушить на удаленный репозиторий? Перед этим всем скачать обновления с удаленного. Зачем тогда нужен pull request, если push и так отправляет данные в удаленный репозиторий?


Ответ

Организация работы, основанная на пулл-реквестах (pull-request, merge-request, запрос на слияние), характерна для распредленных проектов, которые размещаются на специальных хостингах репозиториев. Git сам по себе не имеет такого функционала, он реализуется именно хостингом. Кроме GitHub эту возможность предоставляет Gitlab, возможно есть и другие хостинги. Называется такая организация GitHub flow или Gitlab flow - в противоположность более традиционному Git flow git-flow ("flow" - поток).
Про организацию Git flow можно почитать в описании метки. Кратко: есть основная ветка master, разработчики создают ветки под конкретные задачи (фича, багфикс), потом сливают их в master. Возможно использование ветки-агрегатора develop, в которой копятся изменения до момента очередного релиза.
Git flow реализуем, когда у всех разработчиков есть право создавать новые ветки в удалённом репозитории. Это часто так при закрытой разработке внутри компании, но почти всегда не так, когда проект хостится открыто. Нельзя просто так взять и запушить свою ветку в чужой репозиторий на GitHub.
Другое важное право - сливать изменения в ветку master или другую, из которой код уходит в производство. В открытом репозитории это может делать владелец или группа уполномоченных пользователей. В закрытом код обычно может сливать ответственный за это сотрудник. Перед слиянием код может и должен проходить инспекцию и тестироваться. Если у вас не так - повод обеспокоиться.
Чтобы у разработчиков всё-таки была возможность принимать участие, коммитить свои изменения/дополнения и потом предлагать слить их в основную ветку проекта, были придуманы форки (fork, вилка). Форк - это ваша собственная копия целого репозитория Git. В ней вы можете создавать собственные ветки, делать новые коммиты и всё остальное, что можно делать с принадлежащим вам репозиторием.

Чтобы предложить добавить ваши изменения в основной репозиторий, используется пулл-реквест. Алгоритм создания такой:
Создаете локальную ветку. Создаете в ней новые коммиты (иначе нет смысла в слиянии). Пушите эту локальную ветку на удаленный репозиторий. Заходите на сайт хостинга репозиториев. Там выбираете одну из веток вашего удаленного репозитория и предлагаете влить её в одну из веток другого удалённого репозитория. Как вариант, если у вас есть право создавать ветки, но нет права сливать в master - можно создавать пулл-реквест внутри одного репозитория. Нажимаете кнопку подтверждения.
Теперь создаётся собственно пулл-реквест, как сущность. У него в том числе есть:
Номер и url. Он также появляется в списке открытых реквестов целевого репозитория. Информация о внесённых изменениях в формате diff. Красным подсвечиваются удалённые строки, зелёным добавленные. Есть общая статистика добавленных и удаленных строк.
Возможность оставлять комментарии к любой строке диффа, а также к пулл-реквесту в целом. Эта невероятно полезная возможность позволяет проводить инспекцию кода прямо в пулл-реквесте.
Информация о новых коммитах в виде дерева.
Автоматическая оценка возможности бесконфликтного слияния. Если оно невозможно, придётся вручную разрешать конфликты, как в обычном слиянии. Кнопки, чтобы слить или, наоборот, отклонить запрос.
Изучив ваш пулл-реквест, владелец основного репозитория может выполнить его слияние (а может и нет). Отсюда и название: не вы "выталкиваете" (push) свои изменения в чужой репозитория, а уполномоченный пользователь их "затаскивает" (pull) в него.

Что такое remotes/origin/HEAD?

Вот при вызове команды git branch -a получаю такой вывод
aleksey@aleksey:~/Downloads/NTZ/FittingRoom$ git branch -a develop * master remotes/origin/HEAD -> origin/master remotes/origin/master
Ветка develop и master понятно, что это 2 мои локальные ветки, но понятно, что такое
remotes/origin/HEAD -> origin/master remotes/origin/master
Это удаленные ветки, но в чем их отличия и почему одна HEAD ... Я так понял, что head это та ветка в которую я делаю пуш, но так это должна быть master...
Я так понимаю, что это должно выглядеть так
aleksey@aleksey:~/Downloads/NTZ/FittingRoom$ git branch -a develop * master remotes/origin/master
2 ветки локальные и одна master удаленная...
Зачем head?
ПРАВКА. Добавил еще ветку origin/develop и после выполнения команды git checkout origin/develop получаю вот такой вывод через git branch -a
aleksey@aleksey:~/Downloads/NTZ/FittingRoom$ git branch -a * (detached from origin/develop) develop master remotes/origin/HEAD -> origin/master remotes/origin/develop remotes/origin/master
Я так понял, что HEAD может указывать только на ветки удаленного репазитория... Но почему тогда после переключения на удаленную ветку дев стока HEAD все равно указывает на мастер??
remotes/origin/HEAD -> origin/master
Вот согласно статье на хабре
текущее состояние не изменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD
ниче не понятно, HEAD в итоге указывает на ту ветку где ты находится или на ветку в которую ты сделал последний коммит?


Ответ

ветка (branch) в git — это (плавающий) указатель на commit
HEAD/.git/HEAD (технически) — это файл, содержащий указатель либо на текущую (для репозитория) ветку (например, файл может содержать такой текст ref: refs/heads/master), либо на текущий commit (т.н. detached head, файл содержит строку с хэш-суммой этого commit-а).
удалённый репозиторий ничем не «хуже» вашего локального, и в нём тоже есть такой файл, и информация, выдаваемая командой branch
remotes/origin/HEAD -> origin/master
сообщает вам о том, что этот файл в удалённом репозитории в момент клонирования содержал ссылку на ветку master (в том же, удалённом репозитории).

Я так понял, что head это та ветка в которую я делаю пуш
push вы делаете в ту ветку, которую сами и указали. либо явно, например так:
$ git push репозиторий ветка
либо неявно, «привязав» вашу локальную ветку к (произвольной) ветке в удалённом репозитории, и вызывая git push без дополнительных параметров.
посмотреть «привязки» веток можно, например, командой remote show репозиторий
$ git remote show origin ... Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date)

по поводу дополнения к вопросу
во-первых, я уже ответил практически на тот же вопрос: Почему получаю detached head?
во-вторых, вынесу (и дополню) сюда основное:
эта строка (remotes/origin/HEAD -> origin/master) в выдаче команды branch появляется благодаря наличию в вашем локальном хранилище файла refs/remotes/origin/HEAD, содержащего в вашем случае:
$ cat .git/refs/remotes/origin/HEAD ref: refs/remotes/origin/master
вы можете абсолютно безболезненно удалить этот файл. тогда эта строчка пропадёт из вывода команды branch
файл этот был создан во время клонирования и содержит информацию о том, какая именно ветка была распакована при этом в ваш рабочий каталог вероятно, вы спутали этот файл (.git/refs/remotes/origin/HEAD) с файлом .git/HEAD, который как раз и содержит ссылку на вашу текущую ветку (или на коммит, если ваше хранилище находится в «состоянии detached head»). Я так понял, что HEAD может указывать только на ветки удаленного репазитория.
нет. файл .git/HEAD может хранить:
либо указатель на локальную ветку (т.е., содержать что-то вроде ref: refs/heads/master) либо хэш коммита (т.н. «состояние detached head») Но почему тогда после переключения на удаленную ветку дев стока HEAD все равно указывает на мастер?
remotes/origin/HEAD -> origin/master
ещё раз повторяю: эта строка в выводе команды branch появляется лишь по одной причине — из-за наличия файла .git/refs/remotes/origin/HEAD. этот файл ни на что не влияет. вы его можете абсолютно безболезненно удалить. и тогда этой строки уже не будет в выводе команды branch Вот согласно статье на хабре текущее состояние не изменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD ниче не понятно, HEAD в итоге указывает на ту ветку где ты находится или на ветку в которую ты сделал последний коммит?
последний коммит здесь абсолютно ни при чём.
если файл .git/HEAD содержит указатель на локальную ветку (т.е., содержит что-то вроде ref: refs/heads/master), то, значит, у вас в рабочем каталоге распакован коммит, на который указывает локальная ветка master. хэш этого коммита хранится в файле .git/refs/heads/master если же файл .git/HEAD содержит хэш коммита (т.н. «состояние detached head»), то именно этот коммит сейчас и распакован в вашем рабочем каталоге

Вызов конструктора без скобок

У меня родился вопрос по мотивам вот этого вопроса а точнее комментариев к ним.
Итак вопрос: в чем разница между new T и new T()?


Ответ

Смотрите. new T — это default initialization. При этом происходит следующее: для класса T вызывается конструктор по умолчанию (то есть, конструктор без параметров или конструктор с параметрами, который может быть вызван без параметров т. к. все аргументы имеют значение по умолчанию) для массива T ко всем элементам массива рекурсивно применяется default initialization для остальных T (например, простых типов), ничего не происходит, значение не определено. new T() — это value initialization. Оно работает так: если T — класс, и у него есть хоть один явно определённый конструктор, то вызывается конструктор по умолчанию (см. выше). если T — класс, но не union, и у него нету явно определённых конструкторов, то у него есть автоматически созданный конструктор по умолчанию. Объект сначала подвергается нулевой инициализации (в большинстве случаев, но не всегда, просто память прописывается нулями), а затем вызывается конструктор по умолчанию. если T — массив, ко всем элементам массива рекурсивно применяется value initialization в остальных случаях (например, если T — простой тип), он подвергается нулевой инициализации. Пример: int* p1 = new int; int* p2 = new int(); Здесь значение *p1 не определено, а значение *p2 гарантированно 0. Для полноты: new T(arg) — это direct initialization. Происходит следующее: если T — класс, рассматриваются все конструкторы и вызывается наиболее подходящий иначе T должен быть простым типом, и значение arg приводится к типу T (используя, если нужно, неявные преобразования), и результат будет значением объекта. Заметьте, что в этом случае для классов нулевая инициализация не выполняется, в отличие от случая без параметров, но со скобками.