Как стать автором
Обновить
87
0.1
Евгений Охотников @eao197

Велосипедостроитель, программист-камикадзе

Отправить сообщение

Ну я историю с++ так детально не знаю

Для вас это всего лишь история, для меня -- собственный опыт.

но период до шаблонов вообще можно не считать потому что это не с++, а фигня какая-то, которой пользовались 3 калеки.

Чуть больше, по некоторым оценкам количество программистов на C++ в 1991-ом году оценивалось где-то в 400K.

Ну и эта самая "фигня какая-то" была сильно круче тогдашних конкурентов (коих и не сказать, чтобы было много на конец 1980-х и начало 1990-х).

Забавно, что для некоторых C++ в то время не был отдельным языком программирования, а воспринимался просто как следующая версия Си, в который добавили пару-тройку новых ключевых слов. Тем более, что одним из "selling points" C++ всегда было то, что можно было взять уже имеющийся код на Си и с минимальными переделками начать использовать его как код на C++ (собственно перевод кодовой базы GCC на C++ как раз лишний тому пример). Даже без классов и перегрузки операторов. Просто ссылки вместо указателей.

Так что если бы C++ не выглядел в начале своей жизни именно как нашлепка над Си, то вряд ли он обрел бы такую популярность. Мог бы запросто повторить судьбу Eiffel-я, а то и Modula-3 (при том, что Eiffel не умер, живет себе и здравствует, просто мало кому нужен).

Конечно не причем, наймспэйсы то были

Изначально их не было. Они появились в C++ только в 1990-ом году, когда язык уже стал популярным за счет своей совместимости с Си.

Более того, шаблонов в C++ так же изначально не было. И выкручиваться с обобщенным программированием на C++ тогда приходилось за счет Си-шного препроцессора с define-ами.

Да, и т.е. спустя сколько там 40 лет комитет таки запили модули и ничего не отвалилось.

Об этом рано говорить, модули пока что используют не только лишь все.

Мало того он и сам из хотел сделать, но типа и так пойдет.

Он и auto изначально хотел использовать для автоматического вывода типов. Но "совместимость" с Си не позволила.

Си здесь вообще не причем.

Ну да, ну да. Взлетел бы C++ если бы он бы не помесью Симула и Си, а помесью Симула и Модула-2 (к примеру) -- большой вопрос. По факту же C++ смог несколько десятилетий жить за счет экосистемы Си (начиная от линкеров и форматов объектных файлов, заканчивая бесплатной интеграцией с Си-шным кодом). Как-то странно слышать, что "Си здесь вообще ни при чем".

Но давайте признаем, что виноват на самом деле страус, изобретая с++ он почему то решил что модули это для слабоков

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

Такое тоже будет работать, но что это даёт?

Следование принципу DRY. У вас, по сути, close и деструктор должны приводить к одинаковым результатам. Поэтому логичным выглядит не повторять логику в разных местах, а выражать одно через другое.

Кстати говоря, странно, почему вы оператор перемещения через move_assign делаете, а close -- нет. Можно же close сделать так:

void OFile::close() {
  move_assign(*this, OFile{});
}

И получить те же самые бенефиты, на которых вы зациклены: про добавлении нового поля в класс не нужно делать лишние операции.

Чтобы делать флуширование, необходим доступ к buffer и buffer_pos объекта OFile. Т.е. в ваш handle_holder помимо самого handle, необходимо добавить указатель на объект OFile, std::function или что-то вроде того.

Это потому, что вы увели разговор в сторону. Изначально речь шла о том, что я не вижу смысле в UniqueHandle, который не может сам закрыть хранящийся в нем дескриптор. И показал вам свою реализацию похожего класса.

Вы же сейчас пытаетесь перевести разговор на другой уровень абстракции, когда над дескриптором появляется еще какая-то логика (вроде промежуточных буферов). Очевидно, что handle_holder к этой логике никакого отношения не имеет и не следует в него закладывать еще что-то.

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

Вы так говорите, как будто это что-то плохое. :)(:

Оно выглядит как-то излишне экстремально.

Для заменяемых типов (transparently replaceable) — не нужен. (Т.к. в данном случае имеет место реконструкция объекта того же самого типа, т.е. тип исходного объекта и вновь созданного полностью совпадают.)

У вас move_assign -- это шаблон и вы не знаете, есть ли в типе T константные поля или поля-ссылки. В статье, на которую вы сослались, соответствующая ситуация описана в разделе "Использование std::launder", где рассматривается структура с const int n внутри.

Проблема. Т.к. одного умения делать close, увы, не всегда достаточно. Конкретный пример: деструктор в классе OFile вызывает метод flush(), который записывает все оставшиеся данные в буфере в файл.

Не вижу причин, по которым ваш ~OFile не может быть реализован вот так:

~OFile() { close(); }

Следовательно, в условном Traits::destroy вы можете делать флуширование, если вам это нужно.

во-вторых, что более важно: при добавлении новых переменных-класса в OFile обновлять код реализации operator=(OFile &&) не потребуется.

Да уже понятно что у вас пунктик по этому поводу. Только вот если использовать идиому "make temporary then swap", то модифицировать придется реализацию swap, что все равно полезно.

А вот когда у объекта деструктор вызывается явно просто чтобы сделать присваивание/перемещение, то это добавит приключений тем программистам, которые при разработке добавляют в деструкторы своих классов отладочные печати.

Так что мой вопрос остаётся в силе: было бы интересно увидеть, какую альтернативу моему решению предлагаете вы?

Вам я ничего не предлагаю. Реализация, ссылку на которую я давал, у меня давно используется.

Но, за неимением вашего кода, давайте покажу на примере кода от Microsoft.

Мне доводилось заглядывать в код C++ REST SDK, этого хватило, чтобы скептически относиться к "коду от Microsoft". Так что ссылка на такой себе авторитет.

Сам я предпочитаю реализовывать copy operator и move operator через идиому "make temporary then swap".

Но, что даже более важно, благодаря UniqueHandle можно добавлять в FileHandle новые переменные-класса только в одном месте — в самом классе, т.е. править код move assignment operator и move constructor при этом не нужно, т.к. новые переменные-класса будут учитываться автоматически в сгенерированном компилятором коде move assignment operator и move constructor.

Только вот если вашу реализацию использовать в рамках C++17, то там нужен std::launder, т.к. вызовом деструктора вы прекращаете лайфтайм старого объекта, а возвращенное оператором placement new значение выбрасываете.

Если вы считаете, что UniqueHandle не нужен

Вы невнимательно прочитали: я говорил, что UniqueHandle, который не может сделать close, не нужен. А если он может делать close, то очистка ресурсов в move operator не проблема.

желательно не просто на словах, а в виде конкретного кода

Где-то здесь есть реализация handle_holder.

Спасибо, что нашли время ответить. Но мотивация к появлению move_assign не смотря на обилие текста от меня ускользнула.

Если функцию move_assign() "реализовать" как *dest = std::move(other);

Так ведь можно вообще не иметь move_assign и делать просто *dest = std::move(other) в местах, где вы применяете move_assign.

И нет, "Потому, что UniqueHandle не умеет корректно закрывать handle." -- не оправдание. Нафиг нужен UniqueHandle, который не умеет закрывать хранящийся в нем дескриптор.

Возможно, если не поленюсь, напишу proposal к стандарту C++

Может лучше пожалеть людей из комитета, которым придется читать подобный бред?

Если тип объекта не поменялся (включая cv-квалификации), то std::launder не нужен.

Я так понимаю, что это начиная с C++20 std::launder не нужен. А в рамках C++17 вроде бы еще нужен, даже если тип остается тем же самым (здесь в конце раздела "std::launder and pointer values").

И еще вопрос(ы) по исключениям:

Вы принципиально не наследуете свои исключения от std::exception (или какого-то наследника std::exception)?

Почему вы не используете дерево наследования для своих исключений? Вроде такого:

class FfhException {};
class FileOpenError : public FfhError {};
class WrongFileNameStr : public FfhError {};
class FileIsAlreadyOpened : public FfhError {};
class AttemptToReadAClosedFile : public FfhError {};

Без такой иерархии пользователи могут забабахаться ловить исключения из вашей библиотеки. Особенно если со временем вы туда еще какие-то исключения будете добавлять.

Можно спросить, а почему такая мудреная реализация move_assign?:

template <class Ty> void move_assign(Ty *dest, Ty &&other)
{
    if (dest != &other) {
        dest->~Ty();
        new(dest)Ty(std::move(other));
    }
}

Почему нельзя было просто сделать *dest = std::move(other)?

Кроме того, если мне не изменяет склероз, то после того, как вы сделали placement new для dest, то для дальнейшего использования dest вам следует применить к нему std::launder.

Я вам только что привёл пример простейшего кода

Вы привели пример невалидного кода.

На этом разговор уже закончен. Для вменяемых программистов. Вы же вольны теоретизировать дальше.

а демонстрация того, что в C++ нет никакого способа обеспечения иммутабельности ссылок

Ссылку в C++ вы просто так изменить не сможете. Отсюда и задачка с i, j и ri. Т.е. назвать ссылки в C++ мутабельными -- это от глубокого знания предмета, не иначе.

И речь про ссылки. Не про объекты, ссылки на которые взяты. Именно про ссылки.

У вас может быть ссылка на const- объект. Или ссылка на не-const объект. Но вот поменять значение самой ссылки вы просто так не сможете.

А никакого другого смысла, кроме защиты от ошибок, механизм иммутабельности не несёт вообще.

Мощно, внушаить. Еще что-то такого же масштаба задвинуть сможете?

Конечно, видно, что вы спорите чисто ради спора

Да я вообще не спорю, а показываю несостоятельность некоторых ваших заявлений. Чтобы было лучше понятно, что в C++ вы разбираетесь как "свинья в апельсинах", пардон май френч.

Вам не всё ли будет равно, упадёт ваша программа “валидным и легальным способом” или не очень валидным и даже совсем нелегальным?

Для меня есть большая разница упадет ли программа или нет. Использование трюков с UB прямой путь к падению, поэтому за такие трюки нужно отрывать руки.

Так-то и иммутабельность низачем не нужна, если сразу правильно программы писать.

Не уводите разговор в сторону. Вы полезли судить о C++, вас в очередной раз макнули в то, что вы неправы в своих утверждениях про C++. И сама по себе иммутабельность здесь не причем. Причем ваша псевдо ученость.

Конечно UB. Но вы спросили, как сделать – я вам показал.

Дяденька, а вы вменяемы вообще? Показали как выстрелить в ногу и считаете это нормальным? Ну ахринеть.

Вы свои проекты тоже с такими ключами компилите?

Случается.

Я не знаю, какая была задача у вас

Так я повторю:

int i  = 0;
int & ri = i;
int j = 0;

В одной области видимости есть живые i и j. Нужно сделать так, чтобы ссылка ri, которая ссылалась на i, начала ссылаться на j. Валидным и легальным способом, само собой.

но я вам показал именно то, что хотел – что неизменность ссылок в C++ не гарантируется

Вы показали (в очередной раз) свое незнание предмета разговора.

Да блин, ну ёжкинижеж... Не, ну как так-то?

У вас же там UB.
https://wandbox.org/permlink/IazzAnT1hLsEcGP2

Не говоря уже про то, что задача была вообще другой.

Завязывали бы вы с рассуждениями про C++, а?

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

(а именно, в C++ есть указатели на иммутабельные объекты, иммутабельные указатели на объекты, иммутабельные указатели на иммутабельные объекты, ссылки на иммутабельные объекты, но нет иммутабельных ссылок)

Вы хотите сказать, что ссылки в C++ мутабельны?

Тогда вот вам:

int i  = 0;
int & ri = i;
int j = 0;

покажите как сделать ri ссылкой на j.

Сама концепция уничтожения объекта в практическом отношении востребована в языке C++ только из-за особенностей используемого архаичного механизма ручного управления памятью.

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

Ну да, касательно разработчиков Rust остается только сказать "а мужики-то и не знают"... facepalm.jpg

доступ к константам безопасен из нескольких потоков

Наличие mutable членов структур/классов уже делает картину мира сложнее. Не говоря уже про то, что в C++ нет транзитивной иммутабельности (к счастью или к сожалению), поэтому даже вызов константного метода у константного объекта может вести к изменению каких-то данных:

class mutator {
public:
  void change_something() {...}
  ...
};

class viewer {
  mutator * m_mutator;
public:
  viewer(mutator * mutator) : m_mutator{mutator} {}

  void f() const { m_mutator->change_something(); }
};

mutator m;
const viewer v1{&m};
const viewer v2{&m};

v1.f(); // Значение m могло измениться.
v2.f(); // Значение m могло измениться еще раз.

Так что не все так однозначно, к сожалению.
Но лучше уж иметь такой const, чем не иметь никакого.

Некоторое время назад сделал std::shared_ptr<const some_data> для случая, когда экземпляры some_data разделялись сразу между несколькими потребителями. Грубо говоря, один тред выкачивает данные из источника и формирует std::shared_ptr<const some_data>, после чего этот shared_ptr отсылается всем заинтересованным потребителям (каждый на своем треде). За счет того, что данные константные не нужно париться о том, что кто-то из потребителей начнет их менять.

Что вы хотите проиллюстрировать этим примером?

Что лямбда в C++ спокойно переживает тот контекст, в котором она была создана.

В данном случае, так как вы погрузили свои лямбды внутрь самых настоящих объектов, наследующих элементам std::vector

Что, черт возьми, ты здесь несешь? Сами-то хоть поняли, что сказали?

Лямбда и есть объект. У этого объекта есть тип. Поэтому можно создать std::vector для экземпляров такого типа. И заполнить этот вектор.

Здесь нет никакого наследования.

Потом её ещё и освобождать надо

Не-а. Но это же C++ знать нужно ну хоть чуть-чуть.

Это не реализация лямбд, как таковых, а тот способ, которым вы их используете.

Это реализация лямбд в C++ позволяет мне использовать их вот таким образом.

1
23 ...

Информация

В рейтинге
3 334-й
Откуда
Гомель, Гомельская обл., Беларусь
Зарегистрирован
Активность