Приветствую, хабровчане!
Я хочу предложить к обсуждению идеи о том, как упростить написание серверных программ на C введением дополнительных языковых средств. Полагаю, что эта тема может быть интересна всем разработчикам, которым приходилось иметь дело с написанием многопоточного или асинхронного кода.
На данный момент я практически завершил написание инструментария — генератора парсеров, парсера C и частично C++, — который позволяет приступить к написанию транслятора, поддерживающего языковые расширения, о которых я здесь расскажу. Но перед тем, как продолжить работу, хотелось бы посоветоваться с коллегами по цеху и найти единомышленников.
Речь пойдёт о многотопоточных асинхронных серверах. Асинхронных — то есть использующих логику обслуживания клиентов, основанную на событиях (epoll/kqueue), при которой одним потоком одновременно обслуживается множество клиентов. Многопоточных — значит готовых использовать преимущества современных многоядерных процессоров, выполняя параллельное обслуживание соединений несколькими потоками в рамках одного процесса.
Асинхронная модель обслуживания позволяет создавать серверы, наиболее подготовленные к работе с по-настоящему высокой нагрузкой. Но асинхронный (неблокирующийся) код писать сложнее, чем синхронный, блокирующийся. Кроме того, существенно сложнее писать асинхронный многопоточный код: слишком легко ошибиться при синхронизации доступа к совместно используемым данным, и слишком сложно такие ошибки выявлять. Это приводит к тому, что на практике зачастую разумнее полностью отказываются от поддержки многопоточности.
Накопив определённый опыт написания таких программ, я стал замечать, что решение основных проблем синхронизации потоков решается типовыми способами, применение которых можно было бы автоматизировать на уровне языка, существенно облегчив задачу разработчику. Затем я увидел, что, используя схожий подход, можно упростить написание кода асинхронной обработки событий. Именно из-за видимой общности решений я предлагаю единый набор языковых дополнений для этих не связанных напрямую задач.
Идеи довольно простые, поэтому, чтобы не создавать ощущения сложности, перечислю основные тезисы по дополнениям к C. Эти дополнения я буду называть «языком Mt».
Итак, Mt — это:
Далее расскажу о каждом дополнении подробнее. Некоторые примеры кода привожу на C++, потому что так их легче соотносить с эквивалентным кодом на Mt.
Рассмотрим распространённую схему обработки запросов. Пусть у нас есть некоторый объект класса MyFrontend, представляющий собой реализацию нашего сервера. После получения и декодирования запроса от клиента мы вызываем метод серверного объекта processRequest(). Этот метод выполняет некоторые операции, вызывающие изменение внутреннего состояния сервера. Положим для примера, что в результате обработки запроса увеличивается счётчик кол-ва запросов num_requests:
Mt освобождает нас от необходимости ручной синхронизации:
async object объявляет класс объектов, безопасных с точки зрения многопоточности. Такие объекты будем называть асинхронными. Приведённый пример кода на Mt транслируется в код, эквивалентный приведённому примеру на C++.
Теперь допустим, что для обработки запроса MyFrontend должен вызвать метод doSomething() объекта MyBackend, передав ему в качестве параметра счётчик обращений к бэкэнду num_back. Кроме того, мы не хотим задумываться о возможности взаимоблокировок и хотим, чтобы всё работало с нерекурсивными мьютексами. Для этого нам нужно освобождать state_mutex перед обращением к MyBackend:
То же самое на Mt:
Транслятор Mt самостоятельно вставляет операции со state_mutex и вводит временную переменную для передачи параметра. Оператор call введён для того, чтобы подчеркнуть, что на время вызова асинхронного метода другого объекта мы освобождаем блокировку state_mutex. Кроме того, это позволяет нам явно запретить вызов асинхронных методов в выражениях.
Помимо приведённых примеров автоматизируется ряд других случаев использования блокировок: ожидание событий (события обычно используются в паре с мьютексом, защищающим условие завершения ожидания), вызов асинхронного метода объекта из другого метода того же объекта.
Таким образом, мы сводим к минимуму риск ошибиться при управлении простой блокировкой на состояние объекта. По моим наблюдениям, если синхронизация доступа к объекту осуществляется с использованием не одной, а нескольких блокировок, то скорее всего имеет место неудачное проектирование: следует или разделить объект на несколько самостоятельных объектов, или слить блокировки в одну. Поэтому, автоматизируя простейший случай с одним мьютексом, мы решаем большую часть проблем синхронизации. Возможность выполнять синхронизацию в ручном режиме и с использованием других примитивов при этом
сохраняется.
На мой взгляд, это наиболее интересная и необычная идея. Объясню на примере, иллюстрирующем работу реального сервиса.
Допустим, наш сервер — это фронтэнд фотохостига. Мы хотим реализовать отдачу клиентам HTML-страниц с фотографиями пользователей. На странице отображается информация из анкеты владельца фотографии и сама фотография. Фотографии могут смотреть только друзья владельцев фото. Таким образом, для того, чтобы ответить на HTTP-запрос, нужно собрать нужную информацию, опросив ряд внутренних сервисов.
Алгоритм обработки запроса:
Сделаем набросок реализации на C. В этот пример можно не вчитываться. Его задача — показать, насколько громоздка ручная реализация даже такой несложной асинхронной цепочки обработки:
Получившийся код сложен, его трудно читать. Большая часть времени при его написании была потрачена на рутинное обеспечение нужного порядка выполнения операций.
Эквивалентная реализация на Mt:
Обратите внимание, что мы практически полностью освободились от рутинной работы по организации нужного порядка исполнения. Кроме того, сгенерированный C-код можно будет использовать в многопоточных программах.
В приведённом примере использованы два новых оператора:
Основная работа, которую выполняет Mt при трансляции асинхронных цепочек в C, заключается в следующем:
Параллельное выполнение сложных цепочек операций, непредставимых одной функцией в Mt, можно реализовать, использовав несколько разных асинхронных цепочек. То есть если ветвления в цепочке так сложны, что их не удаётся описать одной функцией, то можно построить описание как композицию нескольких функций.
С помощью Mt мы можем описывать циклы и условные переходы, разрываемые ожиданием ответов от асинхронных вызовов, в такой же простой и привычной форме, как при написании обычного кода. Допустим, мы хотим поискать значение в трёх независимых кэшах поочерёдно:
Таким образом, для асинхронных цепочек обработки Mt может обеспечить качественный скачок в выразительности и удобстве использования по сравнению с C.
Аннотирование — это введение в код дополнительной вспомогательной информации с помощью аннотирующих слов. Аннотирующие слова могут стоять везде, где могут стоять квалификаторы const и volatile. Все аннотирующие слова начинаются с символа '$'. Примеры:
Транслятор Mt является также основой для системы статической проверки кода, которая использует аннотирование для дополнительных проверок.
С точки зрения реализации транслятора, наиболее сложной особенностью C++ по сравнению с C является зависимость семантического значения языковых конструкций от типов данных аргументов. Это касается, в первую очередь, правил перегрузки имён функций (включая перегрузку операторов) и правил выбора шаблонов с учётом частичных специализаций. Такая зависимость означает, что для того, чтобы определить, какая именно функция или шаблон используется в варыжении, нужно вывести типы данных всех аргументов функции или шаблона. Это, в свою очередь, требует полной и точной реализации правил приведения типов. Всё это составляет довольно значительный объём работы.
В то же время, перегрузка имён — спорное свойство языка. Например, от неё отказались создатели языка Go, о чём есть содержательная запись в FAQ.
В Mt не будет перегрузки имён функций и перегрузки операторов, множественного наследования, исключений, RTTI. Шаблоны без частичной специализации реализовать не так сложно, и этого достаточно для того, чтобы можно было создавать обобщённые контейнерные классы, поэтому шаблоны целесообразно включить в язык. В отсутствие перегрузки имён шаблоны станут гораздо более простым и лаконичным инструментом: таких возможностей для метапрограммирования, как в C++, не будет.
Отсутствие перегрузки операторов не даёт возможность реализовать в Mt удобные аналоги «умных указателей», таких как std::auto_ptr и boost::shared_ptr. Взамен на уровне языка предлагается механизм подсчёта ссылок:
В базовом варианте используется простой подсчёт ссылок. Счётчики ссылок встроены
в каждый синхронный или асинхронный объект Mt (object и async object). При таком подходе к управлению ссылками программисту следует избегать создания циклических ссылок, которые приводят к утечке памяти. Для разрыва циклов используются слабые ссылки. В перспективе можно предусмотреть возможность использования более сложного сборщика мусора, но сейчас я не вижу в этом необходимости.
В отсутствие перегрузки операторов нет необходимости поддерживать в Mt ссылки C++ (вида int &a).
Отмечу, что реализация ссылок на уровне языка позволяет получить более полный и удобный механизм, чем можно построить на шаблонах C++.
Я обозначил основные возможности, которые собираюсь включить в язык Mt. Помимо них в проработке есть и другие идеи, направленные на упрощение разработки и уменьшение ожидаемого количества ошибок в программах.
Транслятор языка Mt — проект с открытым исходным кодом. В данный момент он существует в виде парсера подмножества языка C++ и живёт в svn-репозитории mync.svn.sourceforge.net/svnroot/mync/trunk под рабочим названием «scruffy».
Если у вас есть мысли и идеи по добавлению или изменению определённых свойств языков С/C++, то предлагаю их озвучить: возможно, пришло время для их практической проверки.
Приглашаю к участию в проекте. Если вы ищете тему для самостоятельной работы, которая могла бы дать новые знания и опыт, а в случае успеха превратиться в качественный и успешный продукт, то компилятор нового языка может стать отличным выбором. Если же проблемы, на которые нацелен Mt, для вас не очень актуальны, то, возможно, вас заинтересует разработка парсера C++ и системы статического анализа программ на C/C++.
Вместе мы сможем больше!
Благодарю за внимание.
Я хочу предложить к обсуждению идеи о том, как упростить написание серверных программ на C введением дополнительных языковых средств. Полагаю, что эта тема может быть интересна всем разработчикам, которым приходилось иметь дело с написанием многопоточного или асинхронного кода.
На данный момент я практически завершил написание инструментария — генератора парсеров, парсера C и частично C++, — который позволяет приступить к написанию транслятора, поддерживающего языковые расширения, о которых я здесь расскажу. Но перед тем, как продолжить работу, хотелось бы посоветоваться с коллегами по цеху и найти единомышленников.
Речь пойдёт о многотопоточных асинхронных серверах. Асинхронных — то есть использующих логику обслуживания клиентов, основанную на событиях (epoll/kqueue), при которой одним потоком одновременно обслуживается множество клиентов. Многопоточных — значит готовых использовать преимущества современных многоядерных процессоров, выполняя параллельное обслуживание соединений несколькими потоками в рамках одного процесса.
Асинхронная модель обслуживания позволяет создавать серверы, наиболее подготовленные к работе с по-настоящему высокой нагрузкой. Но асинхронный (неблокирующийся) код писать сложнее, чем синхронный, блокирующийся. Кроме того, существенно сложнее писать асинхронный многопоточный код: слишком легко ошибиться при синхронизации доступа к совместно используемым данным, и слишком сложно такие ошибки выявлять. Это приводит к тому, что на практике зачастую разумнее полностью отказываются от поддержки многопоточности.
Накопив определённый опыт написания таких программ, я стал замечать, что решение основных проблем синхронизации потоков решается типовыми способами, применение которых можно было бы автоматизировать на уровне языка, существенно облегчив задачу разработчику. Затем я увидел, что, используя схожий подход, можно упростить написание кода асинхронной обработки событий. Именно из-за видимой общности решений я предлагаю единый набор языковых дополнений для этих не связанных напрямую задач.
Идеи довольно простые, поэтому, чтобы не создавать ощущения сложности, перечислю основные тезисы по дополнениям к C. Эти дополнения я буду называть «языком Mt».
Итак, Mt — это:
- Автоматическое управление мьютексами для синхронизации доступа к объектам;
- Асинхронные цепочки обработки, записываемые как обычные последовательные функции;
- Аннотирование — дополнительное описания элементов программы для статического анализа исходных текстов;
- Встроенный механизм подсчёта ссылок;
- Заимствование наиболее полезных возможностей из C++;
- Совместимость с C, жёсткая привязка к C ABI. Первоначальная реализация — транслятор из Mt в C.
Далее расскажу о каждом дополнении подробнее. Некоторые примеры кода привожу на C++, потому что так их легче соотносить с эквивалентным кодом на Mt.
1. Автоматическое управление мьютексами
Рассмотрим распространённую схему обработки запросов. Пусть у нас есть некоторый объект класса MyFrontend, представляющий собой реализацию нашего сервера. После получения и декодирования запроса от клиента мы вызываем метод серверного объекта processRequest(). Этот метод выполняет некоторые операции, вызывающие изменение внутреннего состояния сервера. Положим для примера, что в результате обработки запроса увеличивается счётчик кол-ва запросов num_requests:
class MyFrontend
{
private:
Mutex state_mutex;
unsigned num_requests;
public:
void processRequest ()
{
state_mutex.lock ();
++ num_requests;
state_mutex.unlock ();
}
};
Mt освобождает нас от необходимости ручной синхронизации:
async object MyFrontend
{
private:
unsigned num_requests;
public:
async void processRequest ()
{
++ num_requests;
}
};
async object объявляет класс объектов, безопасных с точки зрения многопоточности. Такие объекты будем называть асинхронными. Приведённый пример кода на Mt транслируется в код, эквивалентный приведённому примеру на C++.
Теперь допустим, что для обработки запроса MyFrontend должен вызвать метод doSomething() объекта MyBackend, передав ему в качестве параметра счётчик обращений к бэкэнду num_back. Кроме того, мы не хотим задумываться о возможности взаимоблокировок и хотим, чтобы всё работало с нерекурсивными мьютексами. Для этого нам нужно освобождать state_mutex перед обращением к MyBackend:
class MyFrontend
{
private:
Mutex state_mutex;
unsigned num_requests;
unsigned num_back;
MyBackend *backend;
public:
void processRequest ()
{
state_mutex.lock ();
++ num_back;
unsigned tmp_num_back = tmp_num_back;
state_mutex.unlock ();
backend->doSomething (tmp_num_back);
state_mutex.lock ();
++ num_requests;
state_mutex.unlock ();
}
};
То же самое на Mt:
async object MyFrontend
{
private:
unsigned num_requests;
unsigned num_back;
MyBackend *backend;
public:
async void processRequest ()
{
call backend->doSomething (++ num_back);
++ num_requests;
}
};
Транслятор Mt самостоятельно вставляет операции со state_mutex и вводит временную переменную для передачи параметра. Оператор call введён для того, чтобы подчеркнуть, что на время вызова асинхронного метода другого объекта мы освобождаем блокировку state_mutex. Кроме того, это позволяет нам явно запретить вызов асинхронных методов в выражениях.
Помимо приведённых примеров автоматизируется ряд других случаев использования блокировок: ожидание событий (события обычно используются в паре с мьютексом, защищающим условие завершения ожидания), вызов асинхронного метода объекта из другого метода того же объекта.
Таким образом, мы сводим к минимуму риск ошибиться при управлении простой блокировкой на состояние объекта. По моим наблюдениям, если синхронизация доступа к объекту осуществляется с использованием не одной, а нескольких блокировок, то скорее всего имеет место неудачное проектирование: следует или разделить объект на несколько самостоятельных объектов, или слить блокировки в одну. Поэтому, автоматизируя простейший случай с одним мьютексом, мы решаем большую часть проблем синхронизации. Возможность выполнять синхронизацию в ручном режиме и с использованием других примитивов при этом
сохраняется.
2. Асинхронные цепочки обработки
На мой взгляд, это наиболее интересная и необычная идея. Объясню на примере, иллюстрирующем работу реального сервиса.
Допустим, наш сервер — это фронтэнд фотохостига. Мы хотим реализовать отдачу клиентам HTML-страниц с фотографиями пользователей. На странице отображается информация из анкеты владельца фотографии и сама фотография. Фотографии могут смотреть только друзья владельцев фото. Таким образом, для того, чтобы ответить на HTTP-запрос, нужно собрать нужную информацию, опросив ряд внутренних сервисов.
Алгоритм обработки запроса:
- Отправить серверу проверки авторизации cookie пользователя, чтобы идентифицировать его (check_auth);
- После проверки авторизации отправить серверу, обслуживающему социальный граф, запрос на проверку, являются ли пользователи друзьями (check_friends);
- Отправить в хранилище анкетных данных запрос на владельца фотографии (get_userinfo);
- После получения ответов от check_friends и get_userinfo сформировать HTML-страницу и отправить её клиенту.
Сделаем набросок реализации на C. В этот пример можно не вчитываться. Его задача — показать, насколько громоздка ручная реализация даже такой несложной асинхронной цепочки обработки:
// Структура состояния асинхронной цепочки.
typedef struct {
// "Родительский" класс, управляющий счётчиком ссылок.
MReferenced parent_referenced;
// Блокировка на доступ к элементам состояния.
MMutex state_mutex;
uint64_t owner_id;
uint32_t photo_id;
// Если 'true', то прерываем цепочку.
bool cancel;
// Получили ответ от get_userinfo?
bool got_userinfo;
// Карма владельца фото (анкетные данные).
uint32_t owner_carma;
// Получили ответ от check_friends?
bool got_friends;
} HttpPhotoData;
static void http_photo_data_init (HttpPhotoData *data)
{
m_referenced_init ((MReferenced*) data);
m_mutex_init (&data->state_mutex);
data->cancel = false;
data->got_userinfo = false;
data->got_friends = false;
}
static void http_photo__get_userinfo_ret (uin32_t owner_carma,
void *_data);
static void http_photo__check_auth_ret (uint64_t client_id,
void *_data);
static void http_photo__end (HttpPhotoData *data);
// Точка входа в асинхронную цепочку.
void http_photo (char *cookie_str,
uint64_t owner_id,
uint32_t photo_id)
{
HttpPhotoData *data = m_malloc (sizeof (HttpPhotoData));
http_photo_data_init (data);
data->owner_id = owner_id;
data->photo_id = photo_id;
{
m_referenced_ref ((MReferenced*) data);
MCallbackDesc cb;
m_callback_desc_init_refdata (&cb, http_photod__get_userinfo_ret, data);
get_userinfo (&cb, owner_id);
}
{
m_referenced_ref ((MReferenced*) data);
MCallbackDesc cb;
m_callback_desc_init_refdata (&cb, http_photod__check_auth_ret, data);
check_auth (&cb, cookie_str);
}
m_referenced_unref ((MReferenced*) data);
}
// Ответ от get_userinfo.
static void http_photo__get_userinfo_ret (uint32_t owner_carma,
void *_data)
{
HttpPhotoData *data = (HttpPhotoData*) _data;
m_mutex_lock (&data->state_mutex);
if (data->cancel) {
m_mutex_unlock (&data->state_mutex);
return;
}
data->owner_carma = owner_carma;
data->got_userinfo = true;
if (data->got_friends) {
// Все данные для ответа собраны, отвечаем клиенту.
http_photo_end (data);
}
m_mutex_unlock (&data->state_mutex);
}
// Ответ от check_auth.
static void http_photo__check_auth_ret (uint64_t client_id,
void *_data)
{
HttpPhotoData *data = (HttpPhotoData*) _data;
m_referenced_ref ((MReferenced*) data);
MCallbackDesc cb;
m_callback_desc_init_refdata (&cb, http_photo__check_friends_ret, data);
check_friends (&cb, client_id, data->owner_id);
}
// Ответ от check_friends.
static void http_photo__check_friends_ret (bool friends,
void *_data)
{
HttpPhotoData *data = (HttpPhotoData*) _data;
m_mutex_lock (&data->state_mutex);
if (data->cancel) {
m_mutex_unlock (&data->state_mutex);
return;
}
data->got_friends = true;
if (!friends) {
// Отказано в доступе. Прерываем цепочку.
data->cancel = true;
m_mutex_unlock (&data->state_mutex);
return;
}
if (data->got_userinfo) {
// Все данные для ответа собраны, отвечаем клиенту.
http_photo_end (data);
}
m_mutex_unlock (data->state_mutex);
}
// Отправка ответа клиенту.
static void http_photo_end (HttpPhotoData *data)
{
photo_send_page (data->owner_id, data->photo_id, data->owner_carma);
}
Получившийся код сложен, его трудно читать. Большая часть времени при его написании была потрачена на рутинное обеспечение нужного порядка выполнения операций.
Эквивалентная реализация на Mt:
async chain void http_photo (char *cookie_str,
uint64_t owner_id,
uint32_t photo_id)
{
async call GetUserinfoCall get_userinfo (owner_id);
async call check_auth (cookie);
gives uint64_t client_id;
async call check_friends (client_id, owner_id)
gives bool friends;
if (!friends) {
// Доступ запрещён
return;
}
GetUserinfoCall gives uint32_t owner_carma;
// Отправляем HTTP-ответ со страницей фото.
photo_send_page (owner_id, photo_id, owner_carma);
}
Обратите внимание, что мы практически полностью освободились от рутинной работы по организации нужного порядка исполнения. Кроме того, сгенерированный C-код можно будет использовать в многопоточных программах.
В приведённом примере использованы два новых оператора:
- Оператор async call описывает асинхронный вызов. Функции, которые можно использовать с этим оператором, должны иметь особую сигнатуру: первый параметр — callback, который будет вызван по завершении вызова. Callback завершения должен обязательно быть вызван при любом исходе, будь то успешное выполнение вызова, внутренняя ошибка или таймаут.
- Оператор gives задаёт точку, в которой нужно дождаться завершения указанного асинхронного вызова. gives вводит в область видимости список возвращаемых вызовом значений. Если операторы async call и gives должны быть разнесены, то в async call вызов именуется и в gives указывается имя вызова (см. GetUserinfoCall в приведённом примере).
Основная работа, которую выполняет Mt при трансляции асинхронных цепочек в C, заключается в следующем:
- Определяет набор переменных, которые нужно хранить в структуре состояния цепочки (HttpPhotoData);
- Дробит функцию на более мелкие. Точками разрыва являются операторы async call;
- Обеспечивает нужный порядок выполнения, преобразуя операторы C, нарушенные операторами async call и gives, в эквивалентные асинхронные конструкции по фиксированным правилам.
Параллельное выполнение сложных цепочек операций, непредставимых одной функцией в Mt, можно реализовать, использовав несколько разных асинхронных цепочек. То есть если ветвления в цепочке так сложны, что их не удаётся описать одной функцией, то можно построить описание как композицию нескольких функций.
С помощью Mt мы можем описывать циклы и условные переходы, разрываемые ожиданием ответов от асинхронных вызовов, в такой же простой и привычной форме, как при написании обычного кода. Допустим, мы хотим поискать значение в трёх независимых кэшах поочерёдно:
async chain void search (int key)
{
for (int i = 0; i < 3; i++) {
async call search_cache (key, i /* cache id */)
gives int value;
if (value > 0) {
printf ("VALUE: %d\n", value);
break;
}
}
}
Таким образом, для асинхронных цепочек обработки Mt может обеспечить качественный скачок в выразительности и удобстве использования по сравнению с C.
3. Аннотирование
Аннотирование — это введение в код дополнительной вспомогательной информации с помощью аннотирующих слов. Аннотирующие слова могут стоять везде, где могут стоять квалификаторы const и volatile. Все аннотирующие слова начинаются с символа '$'. Примеры:
int * $nonnull ptr; // указатель не может быть равен NULL
MyObject * $heap myobj; // объект должен быть выделен с помощью аллокатора
Транслятор Mt является также основой для системы статической проверки кода, которая использует аннотирование для дополнительных проверок.
4. Заимствование возможностей C++
С точки зрения реализации транслятора, наиболее сложной особенностью C++ по сравнению с C является зависимость семантического значения языковых конструкций от типов данных аргументов. Это касается, в первую очередь, правил перегрузки имён функций (включая перегрузку операторов) и правил выбора шаблонов с учётом частичных специализаций. Такая зависимость означает, что для того, чтобы определить, какая именно функция или шаблон используется в варыжении, нужно вывести типы данных всех аргументов функции или шаблона. Это, в свою очередь, требует полной и точной реализации правил приведения типов. Всё это составляет довольно значительный объём работы.
В то же время, перегрузка имён — спорное свойство языка. Например, от неё отказались создатели языка Go, о чём есть содержательная запись в FAQ.
В Mt не будет перегрузки имён функций и перегрузки операторов, множественного наследования, исключений, RTTI. Шаблоны без частичной специализации реализовать не так сложно, и этого достаточно для того, чтобы можно было создавать обобщённые контейнерные классы, поэтому шаблоны целесообразно включить в язык. В отсутствие перегрузки имён шаблоны станут гораздо более простым и лаконичным инструментом: таких возможностей для метапрограммирования, как в C++, не будет.
5. Ссылки и слабые ссылки
Отсутствие перегрузки операторов не даёт возможность реализовать в Mt удобные аналоги «умных указателей», таких как std::auto_ptr и boost::shared_ptr. Взамен на уровне языка предлагается механизм подсчёта ссылок:
int @a; // ссылка
int @@b; // слабая ссылка
В базовом варианте используется простой подсчёт ссылок. Счётчики ссылок встроены
в каждый синхронный или асинхронный объект Mt (object и async object). При таком подходе к управлению ссылками программисту следует избегать создания циклических ссылок, которые приводят к утечке памяти. Для разрыва циклов используются слабые ссылки. В перспективе можно предусмотреть возможность использования более сложного сборщика мусора, но сейчас я не вижу в этом необходимости.
В отсутствие перегрузки операторов нет необходимости поддерживать в Mt ссылки C++ (вида int &a).
Отмечу, что реализация ссылок на уровне языка позволяет получить более полный и удобный механизм, чем можно построить на шаблонах C++.
Заключение
Я обозначил основные возможности, которые собираюсь включить в язык Mt. Помимо них в проработке есть и другие идеи, направленные на упрощение разработки и уменьшение ожидаемого количества ошибок в программах.
Транслятор языка Mt — проект с открытым исходным кодом. В данный момент он существует в виде парсера подмножества языка C++ и живёт в svn-репозитории mync.svn.sourceforge.net/svnroot/mync/trunk под рабочим названием «scruffy».
Если у вас есть мысли и идеи по добавлению или изменению определённых свойств языков С/C++, то предлагаю их озвучить: возможно, пришло время для их практической проверки.
Приглашаю к участию в проекте. Если вы ищете тему для самостоятельной работы, которая могла бы дать новые знания и опыт, а в случае успеха превратиться в качественный и успешный продукт, то компилятор нового языка может стать отличным выбором. Если же проблемы, на которые нацелен Mt, для вас не очень актуальны, то, возможно, вас заинтересует разработка парсера C++ и системы статического анализа программ на C/C++.
Вместе мы сможем больше!
Благодарю за внимание.