Home

Advertisement

All things considered... - MSVS 2010 beta 1 [entries|archive|friends|userinfo]
Qbit

[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

MSVS 2010 beta 1 [May. 21st, 2009|02:59 am]
Previous Entry Add to Memories Tell a Friend Next Entry

Только что установил сабж. По поводу переписанной под WPF среды я особо распространятся не буду, на то придуманы всякие евангелисты. Меня интересует в основном C++, CLR, FCL, ну и F# постольку поскольку.

C++

Больше всего интересовала реализация замыканий (GCC тут усердно пыхтит в сторонке). Что я могу сказать. Кривовато, но с некоторым скрипом пользоваться можно. И кривость эта относится вообще говоря к Стандарту, а не к реализации. Ну например.

Для реализации «правильных» замыканий нужно как-то согласовывать области видимости самого замыкания и захватываемого окружения. Лямбда ведь может и пережить скоуп замкнутой переменной. Рассмотрим лямбду, которая 1) не только читает, но и меняет в замкнутый контекст, 2) вызывается за пределами видимости захваченной стековой переменной. Допустим, нужен аналог такого C#-кода:

using System;

static class EntryPoint
{
  static Action<int> Foo()
  {
    int i = 3;
    Action<int> λ = (newValue) =>
    {
      Console.WriteLine("Old: {0}, new: {1}", i, newValue);
      i = newValue;
    };

    λ(5);  // Old: 3, new: 5
    Console.WriteLine("Current: {0}", i);  // Current: 5
    i = 8;
    λ(13);  // Old: 8, new: 13

    return λ;
  }

  static void Main()
  {
    var foo = Foo();

    foo(21);
  }
}

Эта программа выведет то, что и ожидается:
Old: 3, new: 5
Current: 5
Old: 8, new: 13
Old: 13, new: 21
Кстати, вопрос на засыпку: как разработчики добились такого поведения? Ведь захваченная стековая переменная должна разушиться при выходе из функции, значит по ссылке передавать нельзя. С другой стороны, по значению тоже нельзя, ведь тогда бы читались и модифицировались разные копии переменной (равно как и при боксинге). А вы говорите Саттер :)

При объявлении лямбды в C++0x можно явно перечислить захватываемые переменные в квадратных скобках. А можно просто поставить значок «=» или «&» для захвата всех замыкаемых переменных по значению или по ссылке соответственно. Указание захвата по значению в нашем примере приведёт к ошибке компиляции при попытке изменить контекст (i = newValue;). Остаётся захват по ссылке:

#include "stdafx.h"

std::tr1::function<void (int)> Foo()
{
  int i = 3;
  auto const λ( [&](int newValue)
  {
    std::cout << boost::format("Old: %1%, new: %2%\r\n") % i % newValue;
    i = newValue;
  } );

  λ(5);
  std::cout << boost::format("Current: %1%\r\n") % i ;
  i = 8;
  λ(13);

  return λ;
}

int main()
{
  auto const foo( Foo() );

  foo(21);
}

Увы, поведение этого кода Стандартом не определено по указанной ранее причине — обращение по ссылке на уже разрушенный стек-фрейм. У меня, например, получилось следующее:
Old: 3, new: 5
Current: 5
Old: 8, new: 13
Old: -858993460, new: 21

О прочих нововведениях как-нибудь в другой раз. А их там немало. При переходе от второго фреймворка к третьему менялись только библиотеки и языки, версия же CLR оставалась 2.0.50727. В новом же фреймворке CLR обогатилась некоторыми прикольными фичами.

LinkReply

Comments:
[User Picture]From: [info]wizzard0
2009-05-21 01:42 am (UTC)

(Link)

>> как разработчики добились такого поведения?

вроде, генерируется класс, время жизни которого совпадает с временем жизни к(л)ожуры, в него пихаются полями все переменные, попадающие в замыкание и заменяются все ссылки на них - ссылками на переменные этого экземпляра.

то есть, на самом деле они из стека переезжают в хип.

если внимательно читать стандарт C#, то [b]нигде не сказано, что локальные переменные выделяются на стеке[/b]. они там _могут_ выделяться как оптимизация. но вообще value semantics означает именно что они by value, и никоим образом не "форсирует" то, что они в стеке.

поэтому в этом случае остальное поведение переменных остается полностью согласно стандарту и всё хорошо

а вот как оно себя ведет при re-entrancy и потоках - не знаю. не хочу думать :)
[User Picture]From: [info]109
2009-05-21 08:48 am (UTC)

(Link)

а вот как оно себя ведет при re-entrancy и потоках - не знаю. не хочу думать :)

так а что там думать - это инстанс анонимного класса, так себя и ведёт.
[User Picture]From: [info]wizzard0
2009-05-21 11:42 am (UTC)

(Link)

ну инстанс это реализация, мало ли, вдруг они какую семантику специальную имеют
[User Picture]From: [info]bik_top
2009-05-21 09:23 am (UTC)

(Link)

>нигде не сказано, что локальные переменные выделяются на стеке

Дело не в том, локальные они или нет, а в том, value-тип или reference-тип. Стандарт Ecma на CLI и C# я детально не изучал, но Рихтер говорит, что переменная value-типа размещается или на стеке, или в куче, если она входит в состав reference-типа. (Кроме того, в стандарте на CLI стек весьма активно фигурирует.)

Так вот, в этом примере создаётся обманчивое впечатление, что переменная val-типа размещается на стеке. На самом же деле, она размещается в куче в составе класса, неявно созданного под замыкание.
[User Picture]From: [info]wizzard0
2009-05-21 01:45 am (UTC)

(Link)

ну то есть строго говоря в сишарпе не подразумевается даже существование стэка - return ведь можно сделать и без него

это implementation details, которое, правда, влияет на вызов диспоз и финализаторов у IDisposable обьектов, обьявленных локально.

кстати!!!

надо сделать переменной stackalloc и запихать ее в кложуру, и посмотреть :-D
[User Picture]From: [info]109
2009-05-21 08:49 am (UTC)

(Link)

а поподробнее про диспоз и финализаторы можно?
[User Picture]From: [info]bik_top
2009-05-21 08:59 am (UTC)

(Link)

>а поподробнее про диспоз и финализаторы можно?

Наверное, имеется в виду, что если лямбда захватывает переменную, скоуп которой вводится конструкцией using, то всё будет плохо.
[User Picture]From: [info]109
2009-05-21 09:08 am (UTC)

(Link)

причём здесь лямбда, вообще? я вот про это спрашиваю: [stack] - это implementation details, которое, правда, влияет на вызов диспоз и финализаторов у IDisposable обьектов, обьявленных локально.
[User Picture]From: [info]bik_top
2009-05-21 09:13 am (UTC)

(Link)

Согласен, фраза некорректная.
[User Picture]From: [info]109
2009-05-21 09:39 am (UTC)

(Link)

не, я вовсе не говорю, что некорректная. я лично не знаю ничего про упомянутые детали имплементации, но мало ли чего я не знаю. вот я и попросил поподробнее.
[User Picture]From: [info]zabivator
2009-05-21 06:16 pm (UTC)

(Link)

Мне кажется, или в таком случае будет происходить расшаривание переменной, т.е. если она лежала на стеке - переезжает в хип, соответственно финализация будет происходить когда кончаться ссылки на переменную.
[User Picture]From: [info]bik_top
2009-05-21 08:48 pm (UTC)

IDisposable и using

(Link)

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

Финализация не при чём. Может, я неправильно тебя понял, но на всякий случай — маленький ликбез :)

Синтаксис шарповых финализаторов разработчики языка сделали похожим на синтаксис плюсовых деструкторов. И это несомненная ошибка Хейлсберга, потому что финализатор не является аналогом плюсового деструктора. Ближайшим родственником плюсового деструктора является метод Dispose(). Именно он позволяет воплощать идиому RAII.

В шарпе есть конструкция using, которая позволяет гарантировать вызов метода Dispose() при выполнении одного из условий: 1) достижение конца скоупа, 2) выход по return не доходя до конца скоупа, 3) выход из скоупа при возникновении исключения. То есть имеем прямую аналогию с механизмом вызова плюсовых деструкторов. В то время как момент вызова финализатора (если он вообще есть) неопределён (когда там сборщик сработает), вызов Dispose() при использовании using строго детерменирован. Для освобождения неуправляемых ресурсов в C# нужно использовать именно Dispose(), финализатор играет вспомогательную роль, он может как присутствовать, так и отсутствовать.

А теперь приведу пример проблемы, которую я имел в виду. Пусть есть класс, реализующий IDisposable:

class A : IDisposable
{
  private bool _valid;
  public bool Valid { get { return _valid; } }
  public A()
  {
    Debug.WriteLine("A..ctor()");
    _valid = true;
  }
  public void Dispose()
  {
    Debug.WriteLine("A.Dispose()");
    _valid = false;
  }
}

(Здесь вместо «_valid = true;» может стоять захвать какого-нибудь ресурса, а вместо «_valid = false;» — освобождение). Тогда нижеследующий код напечатает в начале скоупа «A..ctor()», а по выходу из скоупа «A.Dispose()»:

using (var a = new A())
{
  ...
}

Проблема в том, что при копировании ссылки расшаривания владения произвольным ресурсом само по себе не происходит. Поэтому следующий код напечатает «Is valid: False»:

static class EntryPoint
{
  static Func<bool> Foo()
  {
    using (var a = new A())
    {
      return () => a.Valid;
    }
  }

  static void Main()
  {
    var foo = Foo();
    Console.WriteLine("Is valid: {0}", foo());
  }
}
[User Picture]From: [info]zabivator
2009-05-21 09:20 pm (UTC)

Re: IDisposable и using

(Link)

0) То, что ты написал, я знаю - спасибо [info]ivansorokin
Где проблема-то? Вижу два варианта:
1) Dispose вызовется при разрушении foo()
2) Объект скопируется, будет два. У одного Dispose вызовётся при выходе из Foo, у другого при сносе foo() (a-la gc)
Какой вариант верен?
[User Picture]From: [info]bik_top
2009-05-21 09:49 pm (UTC)

Re: IDisposable и using

(Link)

>Где проблема-то?

Проблема подытожена фразой «Проблема в том, что...» из предыдущего коммента.

>Dispose вызовется при разрушении foo()

Что такое «разрушение foo()»? При разрушении foo ничего не вызовется.

>Объект скопируется, будет два.

В данном примере никакие объекты не копируются, только ссылки.

>У одного Dispose вызовётся при выходе из Foo

Не при выходе из Foo(), а при выходе из using-скоупа. Если его там не будет, никакого Dispose() не вызовется.

>у другого при сносе foo() (a-la gc)

Ты о чём? 0_о Здесь не определён финализатор! Поэтому сборка этого объекта сводится просто к освобождению памяти. Ничего вызываться не будет.
[User Picture]From: [info]zabivator
2009-05-22 05:32 am (UTC)

Re: IDisposable и using

(Link)

Не при выходе из Foo(), а при выходе из using-скоупа. Если его там не будет, никакого Dispose() не вызовется.
&&
Ты о чём? 0_о Здесь не определён финализатор! Поэтому сборка этого объекта сводится просто к освобождению памяти. Ничего вызываться не будет.
ОЙ! Семантика языка похакана =)
[User Picture]From: [info]bik_top
2009-05-22 06:11 am (UTC)

Прискорбно, но факт

(Link)

>ОЙ! Семантика языка похакана =)

Да, C# местами более переподвывернут, чем C++.
[User Picture]From: [info]zabivator
2009-05-22 07:08 am (UTC)

Re: Прискорбно, но факт

(Link)

Кто-то писал, что сложность шарпа и жабы уже сравнима с плюсовой. Иногда я думаю, что это правда =)
[User Picture]From: [info]wizzard0
2009-05-21 11:47 am (UTC)

(Link)

мне кажется, что если фина... а, тьфу, сорри. my bad. если обьект имеет финализатор, то он уже находится в хипе
[User Picture]From: [info]mejedi
2009-05-21 09:17 am (UTC)

(Link)

C++ стиль выдержан :)
Интересно, этим хоть как-то пользоваться можно будет?
Кстати, они же еще GC прикрутили, так что в принципе могли бы сделать нормальные closures.
Главный вопрос - нахрена такие фичи в низкоуровневом языке.
[User Picture]From: [info]nponeccop
2009-07-22 01:18 pm (UTC)

(Link)

Ну как же "нафига"? Алгоритмы STL есть? Есть! А пользоваться ими можно? Нельзя, т.к. надо писать стопицот букв. Вот для этого и сделали.

Типичный юс-кейс - такой вот заинкапсулированный цикл:

int y = 0;
std::for_each(container, [&](std::string &x)
{
   y += x.size();
});


А инкапсуляция циклов нужна на каждом шагу, именно копипаст стандартных циклов - основная часть неустранимого копипаста в С++.
From: [info]ptllttpilgri
2009-07-18 01:37 am (UTC)

(Link)

Журнальчик прикольный у вас, можно было бы уже и на собственный домен перебираться

Advertisement