Как стать автором
Обновить

Комментарии 82

Но что, если в дальнейшем нам потребуется распарсить не только json, но и xml? Как решить данную задачу? Передавать дополнительный булев параметр isJson?

class A {
public:
A(const string& jsonOrXml, bool isJson);
};


А вариант использовать другой класс не рассматривается? Либо делать два разных метода в классе?

class A {
public:
A();
void ImportJson(const string& json);
void ImportXML(const string& xml);
};
А вариант использовать другой класс не рассматривается?

Но результатом должна быть одна структура/класс. То есть мы должны получить из разных форматов одну структуру
Либо делать два разных метода в классе?

По сути я это и предлагаю. Вместо конструкторов делать функции/статические методы
Но тогда это уже не проблема конструкторов/деструкторов, а проблема стиля программирования. Делать из них комбайны плохой стиль. Чем они проще, тем меньше граблей. Полу-созданные/полу-удаленные объекты могут приподнести много сюрпризов, и некоторые об этом забывают.
В том то и дело, что не нужно делать полу-созданные объекты. Функция должна возрващать целиком сконструированный объект
Конструктор должен создавать объект, а не парсить/вытягивать из бд/реализовывать какой-либо ещё функционал. Если нужно произвести подготовку данных — это должно быть сделано до вызова конструктора.
Да, про это и есть моя статья
Я считаю, не нужно помещать в конструктор код, который может сбойнуть от входных данных. Например, что будет, если передать невалидный xml/json? По идее, конструктор должен выкидывать исключение, но исключения в конструкторе — плохая идея, потому что там есть свои подводные камни.
Если же инициализация объекта xml/json-данными будет отдельной ф-цией, эта ф-ция может и статус возвращать, и принимать дополнительные параметры.
Да, но именно про это и была моя статья. Очень странное чувство, что хабр мне показывает какую-то другую статью, в отличие от остальных читателей :) Но видимо такая уж дурацкая у меня форма подачи, что не смог выделить ключевые моменты
Можно тайпдефы использовать чтобы разделить какой тип мы передаем в конструктор — джсон или хмл.
Можно передавать не строку а контейнер.
Можно все разнести по разным классам с общей базой.
И еще пачка архитектурных решений.

А вот статические методы — зло.
Одними typedef-ами тут не обойтись.
using Json = string
using Xml = string
это все один и тот же string

Можно передавать не строку а контейнер.

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

Можно все разнести по разным классам с общей базой.

По мне довольно плохое решение

А вот статические методы — зло.

А почему они зло?
Муторно флаг делать, а потом вспоминать что он там значит ))) А делать декомпозицию — правильно. Если в ней есть необходимость.
Статические методы — зло — потому что вносят хаос и беспорядок. А в вашем случае вообще не решают проблемы.

Есть strong typedef.

Да, но для них нужно отдельную библиотеку подключать
НЛО прилетело и опубликовало эту надпись здесь
Взывая к более типизируемым языкам… В хаскеле есть именованный конструктор типа. А в Идрисе 1, Идрисе 2, Агде?
НЛО прилетело и опубликовало эту надпись здесь

В Delphi можно создавать несколько конструкторов и вызывать их напрямую.

А почему они зло?
В общем случае, статические методы не зло. Но в данном случае они создают на ровном месте ненужное копирование (пусть даже перемещение, всё равно не бесплатное) только что сконструированного объекта. Надо следить, чтобы не накосячить с этим. Часто видел, что RAII-классы специально запрещают своё копирование/перемещение.
Конкретно в данном случае это особенность представления данных, если нам нужно вернуть ошибку, от этого никуда не деться. Да и в этом случае возможно будет RVO или что-то вроде этого, а если делать без Optional, то RVO будет точно.
В любом случае, заботиться о скорости работы именно на этом этапе разработки программы бесмыссленно. В том смысле, что лучше убирать «бутылочные горлышки» точечно, а не жертвуя выразительностью ради производительности
Но результатом должна быть одна структура/класс. То есть мы должны получить из разных форматов одну структуру
Для этого и существуют паттерны проектирования. Например — фабрика.

Если у нас вдруг добавится парсинг YAML? Тоже добавлять метод в класс? В идеале, конечно, иметь какую нибудь рефлексию и стандартные (де)сериализаторы в разные форматы. Но загрязнять класс парсингом из произвольных форматов, имхо, так себе вариант.

Для этого процесс сериализации отделяют от самого сериализуемого объекта. Один из примеров — библиотека Cereal.

А вариант использования Command отбрасывается?

A(const std::string& s, AbstractParser&), к примеру?

А если использовать специфики С++ с так Вами ругаемыми шаблонами, то можно получить ещё более выразительный код, который, к тому же, будет эффективнее.

template<typename Parser> A(const std::string& s);

Можно и так, но не всегда это требуется. Обычно бывает так, что класс все время десерилизуется из json. И для этого вводить этот паттерн не требуется. А потом появляется второй формат…
Плюс, как я помню этот подход, там все равно используется фабричный метод, а не конструктор

В общем случае идея одна: грамотное проектирование — решение всх проблем, поэтому статья мне кажется неуместной и откровенно глупой, Вы уж простите. Все проблемы, которые вы перечислили не проблемы вовсе, если заранее думать как писать и использовать инструменты там, где их нужно использовать.


Проблемы с конструктором

В смысле деструктор не вызовется? Как это? https://isocpp.org/wiki/faq/exceptions#ctor-exceptions, да и вообще, Вам, наверное, стоит почитать про RAII.


Шаблоны.

Ну тут я вообще не понял, в чем проблема-то? Не используйте их, если в данном месте они не нужны. С таким же успехом можно нагнать на что угодно, вот смотрите: "макросы нечитаемый отстой", попробуйте доказать, что это не так. Да и вообще, вы знаете инструмент удобнее, чем шаблоны для генерации кода? Я — нет. Потому что других механизмов нет, и это, отнюдь, не боль для программиста, если программист знат где их использовать, а главное умеет это делать.
Соглашусь с нечитаемостью, да, синтаксис морально устаревает и с развитием языка становится всё более громоздким, но это всё не бОльшая претензия, чем говорить, что в Java очень длинные имена классов.
Кроме того, есть такая замечательная штука как концепты в С++20, если вас не устраивают шаблоны, посмотрите на них.


Виртуальные функции

Вот этот отрывок информации меня просто сразил. То есть Вы говорите, что вот, у нас в C++ должны быть только интерфейсы, что мы против полиморфного поведения, мы отрицаем добрую часть работы "Банды четырех?", все механизмы vtable — отстой, а stl с их basic_classname неправы и добавляют боли программистам (сюда же и поголовные шаблоны в stl)? Какая глупость.


Резюмирую: мне данная статья кажется некомпетентной. Всё Вами перечисленное — проблемы проектирования и неумения применять инструменты языка.

Вообще, мне кажется, что если есть какая-то вероятность десериализовать из нескольких разных форматах, то хорошо использовать какое-то промежуточное представление дерева, в который парсить и из которого разбирать.
Так получается, что есть у нас десятка три класса, умеющих разбираться из json. Светлым умам пришла идея читать из XML. В три десятка классов добавляем чтение? А потом плюс класс, добавляем в него две десериализации. Звучит грустно, если только вы не на почасовой оплате.


Но если очень надо всё делать в классе, то можно, наверное, так:


class Foo
{
public:
  explicit Foo(JsonParser const&);
  explicit Foo(XmlParser const&);
};

Зачем обязательно передавать строку?

А можете пояснить про невозможность понять откуда вызывается деструктор? Давно на плюсах не пишу, но разве современные IDE и анализаторы не могут показать сразу все вызовы?
По крайней мере Qt не смог. C++ такая вещь. Тут бы обычные вызовы методов все отыскать, а с деструкторами вообще швах
А ну да, там же delete, а не прямой вызов метода…

Все в C++ хорошо ищется, это недоработка QtCreator а, в clang code model деструктор найти проблемы не составляет, но т.к. Qt code model такое не поддерживает то и IDE отстаёт

void foo() {
    auto obj = std::make_unique< object >();
    obj->foo();
}
где тут вызов деструктора IDE найти сможет?
Да да, я забыл что в плюсах не прямой вызов, как с методами ) Спасибо за пример.
из деструктора нельзя вернуть результат выполнения никаким образом, даже через исключение.
Думается, или понимание деструктора не корректно или архитектура.
Это удаление объекта, ни чего более. Нельзя удалить обхект на половину и сказать «я дальше я не шмогла».
Собственно следующее, как по мне, говорит именно об архитектурных проблемах:
также часто бывает, когда объект «застревает» где-нибудь в кэше, из-за чего вызова деструктора можно не дождаться вовсе
Деструктор не может быть не вызван. Объект или удаляется или нет, С++ не имеет сборщика мусора.
Не всегда деструктор используется как удаление объекта. Иногда это какой-то финализатор, например «перед любым выходом из функции выполнить такой код»
Вы меня извините…
Я понимаю, что «если нельзя, но очень хочется..» — но здесь нужно принимать все последствия подобных «лайфхаков» на свой страх и риск.
ок, и что делает этот финализатор?
Много чего может делать. В стандартную библиотеку даже специально добавили класс std::finally
Я собственно к тому именно и призываю, чтобы не писать в деструкторах код, который занимается чем-то помимо освобождения памяти, но не всегда это возможно
В стандартную библиотеку даже специально добавили класс std::finally
или я отстал от последних изменений, или вы с Rust путаете.
я это видел и знаю, но это не пространство имён std, а gsl, отличный вариант, но, к сожалению, не часть стандартной библиотеки… пока что.
Файл закрывать при деструкции объекта, за него отвечающего должен кто?
И, увы, файл можно закрыть наполовину.
А это уже как раз архитектурный вопрос. Если что-то может «закрыться только наполовину» стоит ли помещать это в деструктор, обладающий бинарной логикой?
Для конструкторов есть длинная и холиварная цепь дискуссий «c-tor vs c-tor+init()», но почему-то многие упускают эту же логику для деструктора.

А зачем знать все места, где вызовется деструктор? Мы знаем правило, что когда закончится лайфтайм. Если где-то объект зависает, то это ж не проблема деструктор а.

Ну бывает нужно, если программа многопоточная и какие-то методы объекта могут вести себя по-разному в разных потоках (например, какие-то методы не thread-safe и вызывать их из другого потока нельзя).
Бывают и другие примеры даже в однопоточной программе, но вспомнить детали реализации я не могу
Только у меня сложилось впечатление что автор вляпался в проект на C++ с кривой архитектурой и считает что виноват в этом C++, а не те кто его так использует?
Не только у вас ) Часть проблем реально архитектурная, а не языковая.

Как сказать.. С++ сложный. И гибкий. Это его достоинства или недостатки? ))

А где я писал, что виноват C++? C++ довольно гибкий язык, но эту гибкость нужно использовать с умом
В заголовке написали. Такой заголовок подразумевает что виноваты именно особенности языка. А по факту большинство проблем, описанных вами — архитектурные, и вполне встречаются в других языках с аналогичным функционалом.
Вполне возможно вы не имели этого ввиду, но воспринимается именно так :-)

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

В смысле? Подменить std::vector на свою реализацию? В чем вообще проблема шаблонов? Кроме того что их можно криво написать т.к. это проблема любого языка а не шаблонов в частности.

Подменить std::vector на свою реализацию?

Тут довольно тонкая грань, где от шаблонов избавляться стоит, а где не стоит. Конечно, от vector избавляться не нужно, так как он несет в себе очень много преимуществ. Но заметьте, вся стандартная библиотека построена так, что вы можете подсунуть туда любую структуру данных, даже самописную и без шаблонов. Вот это — правильная архитектура. А представьте, если бы везде в библиотеке требовался бы именно vector

Кроме того что их можно криво написать

Много чего помимо этого. Замедление скорости компиляции, очень приятные ошибки на 10 экранов, невозможность отдебажить ошибку в отличие от обычного кода, высокий порог входа…

std::map (и не только он) к примеру использует std::pair в итераторе, заменить его не возможно, хотя очень хочется


99% шаблонов что я видел не были template< template< > > так что я утверждаю что большинство шаблонов принимают обычные типы.
Взять тот же boost::fusion — им можно пользоваться не написав ни одного шаблона. Это будет куча повторяющегося кода но всё таки оно заведётся, вопрос в том будет ли от этого код лучше.


Высокий порог входа — согласен, как мне кажется он выше чем мог бы быть именно из за заявлений "шаблоны не нужны".
Замедление скорости компиляции — покажите мне вариант без шаблонов выполняющий те же самые операции и при этом компилирующийся быстрее.
Ошибки на 10 экранов — соглашусь частично, С++ так уж устроен что даже когда ты случайно вместо double втыкаешь int ты можешь получить километр ошибок. Шаблоны обычно пугают этим ещё больше т.к. их вывод обычно длиннее. Работать с ошибками в шаблонах надо так же как с и ошибками в коде без шаблонов — смотрим на первую, исправляем, остальное игнорируем. В большинстве случаев проблема шаблона видна в первых 8 строчках даже если вы любите шаблоны с кучей аргументов. Согласен, есть случаи когда разваливается SFINAE и в предельном случае приходится читать тонны вывода чтобы понять что не так, опять таки — грамотное проектирование шаблонов может сократить такое количество случаев, например стратегически расставленными static_assert.
Невозможность отдебажить шаблон спорная — с одной стороны стандартные компиляторы такой фичи не имеют, с другой стороны вроде как в студии уже что-то для этого появилось, и отдельные утилиты для этого имеются. Ну и в принципе шаблоны очень легко покрываются тестами (которые даже запускать не надо).

НЛО прилетело и опубликовало эту надпись здесь

Кодогенерация в С++ осуществляется внешними инструментами и ни как не относится ни к компиляции, ни к шаблонам. Я могу привести примеры не удачного SFINAE когда различные шаблоны действительно дают разное время компиляции, проблема в том, что за исключением тривиальных случаев SFINAE вырождать в руками написанные типы мягко говоря не практично.

НЛО прилетело и опубликовало эту надпись здесь

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

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

Например, функции f2 могут быть нужны элементы A1 и A3, и не нужен элемент A2. Зачем же тогда его указывать в заголовке функции?

или просто их не использовать? Кстати, structured bindings вам в помощь

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

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

Но что, если в дальнейшем нам потребуется распарсить не только json, но и xml? Как решить данную задачу? Передавать дополнительный булев параметр isJson?

сами проблему придумали, сами её и победили... То, что конструктор создает объект, не значит, что его нельзя создать, подготовить и вернуть из функции.

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

во-первых, можно (noexcept(false) и кидаем), во-вторых, это запрещено по умолчанию и не приветствуется не просто так. Например потому, что в случае вызова деструктора в процессе раскрутки стека из-за исключения программа прервется (т.к. не может быть двух исключений одновременно). Это даже не говоря о том, что RAII спроектирован для того, чтобы вы не задумывались о ручном разрушении объектов, а в случае с кидающими деструкторами вам придется об этом думать.

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

опять же, вы не должны хотеть пытаться отследить вызовы деструктора

Во-первых, следует разделять RAII и не-RAII деструкторы

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

Что же делать с деструкторами, которые не являются RAII? Здесь можно обратиться к языку rust и увидеть, что в нем нету требования на обязательный вызов деструктора. Деструктор может как вызываться при выходе из скоупа, так и не вызваться, если мы его вызов где-то раньше отменили. Я предлагаю действовать похожим образом. 

это называется "утечка" и в общем случае является ошибкой. И это абсолютно точно является ошибкой если вы хотите отменять деструктор в случае ошибки.

Такое чувство, что мне не удалось донести своей статьей свои мысли. Потому что то, что пишите Вы, и другие комментаторы, почти все из этого я и пытался донести до читателя, за исключением некоторых пунктов.
Да, читая статью и комментарии у меня складывается ровно такое же ощущение :)

вы очень подробно написали процесс борьбы с ветряными мельницами, вот только на вопрос "а зачем это нужно?" не ответили.

Для того, чтобы писать более простой и понятный код. Зачем это сделано в rust например? Там многие идеи выглядят похожим образом
Для того, чтобы писать более простой и понятный код
у вас проблемы надуманные, а методы решения по большей части неортодоксальные. Я, пытаясь оценить ваш пост с точки зрения плюсовиков разного уровня, понимаю, что те, кто теоретически мог бы допускать озвученные ошибки, просто не поймут половину советов, а вторую половину лучше бы и не понимали.
Зачем это сделано в rust например? Там многие идеи выглядят похожим образом
какие идеи то? Создание объекта через конструктор и специальным методом не шибко принципиально отличается (ну, кроме инициализации базовых классов, но в расте такого концептуально нет). Это не делает конструкторы какими-то плохими. Вот возьмем ваш же пример:
соломенное чучело A
class A {
public:
    A(const string& jsonOrXml, bool isJson);
};
вот вы предлагаете заменить конструктор на метод, верно?
proof of concept
class A {
    A(const string& jsonOrXml, bool isJson);
public:
    static A from(const string& jsonOrXml, bool isJson) {
        return {jsonOrXml, isJson};
    };
};
лучше не стало, правда же? В итоге оказывается, что побеждать надо было не конструктор, а плохую сигнатуру.

Или ваш пример с отменяемыми деструкторами… Вот например я могу написать такую вот ахинею:
Warning: explicit content
void forget(std::move_constructible auto& v) {
    using T = std::remove_cvref_t<decltype(v)>;
    typename std::aligned_storage<sizeof(T), alignof(T)>::type s;
    new (&s) T(std::move(v));
}
который по большому счету будет делать плюс минус то же самое что и rust'овский mem::forget, с поправкой на семантику и требования плюсов. Однако такой функции не просто так нет — просто «забыть» про ресурс это всегда ошибка.
НЛО прилетело и опубликовало эту надпись здесь
Я чё-т не уверен, что у вас там не UB
конечно же UB, но мы и добиться хотим херни. Например мы можем добиться проезда если там была циклическая ссылка. Ну можно в куче выделять конечно...
Сторедж для объекта сдыхает раньше, чем его лайфтайм заканчивается, разве нет?
идея и была в том, чтобы «выключить» деструктор, то есть лайфтайм никогда не заканчивался, а сторедж всё равно освободить надо.
Однако такой функции не просто так нет — просто «забыть» про ресурс это всегда ошибка.

Простите, а как вы предлагаете реализовать вот такой метод без использования forget/ManuallyDrop?

ну в с++ с реализацией подобного метода не было бы абсолютно никаких проблем — указатель data засовывался бы в unique_ptr<T[]> и занулялся вместе с size/capacity.

Однако rust-версия вызывает у меня полное недоумение. Во-первых, зачем вообще нужен Box<[T]> и зачем в него конвертировать? Почему нельзя было реализовать метод так же, как его сделали бы в плюсах? Почему нельзя было просто достать Box<[T]> из RawVec внутри Vec? Почему там вызывается shrink_to_fit, который еще и реализован не через realloc? Половина приседаний кажется попросту лишней…

Если его достать то он будет связан владением с вектором. Box<[T]> имеет фиксированный размер, поэтому если ему дать кусок памяти большего размера то память немножко утечет.

Идея в том что Vec динамический, так что можно удобно собрать вектор а затем превратить его в бокс массива

Если его достать то он будет связан владением с вектором.
ну по сути это чисто семантическое решение чисто семантической проблемы. Причем довольно-таки костыльное, как по мне. Могли бы ввести возможность помечать методы в качестве разрушающих, и осталось бы только переопределить лайфтаймы объектов (это раст вроде умеет?)
Box<[T]> имеет фиксированный размер, поэтому если ему дать кусок памяти большего размера то память немножко утечет.
аллокаторы немножко не так работают — освобождается весь буфер независимо от того, каким вы считаете его размер.
Идея в том что Vec динамический, так что можно удобно собрать вектор а затем превратить его в бокс массива
Но зачем? Сэкономить 8 байт на стеке? Ценой копирования вектора?
Могли бы ввести возможность помечать методы в качестве разрушающих
Мне кажется подобное поведение в принципе в расте сделать можно, но в конкретном случае из за динамической природы vec оно практически бесполезно — практически всегда размер вектора не совпадает с размером его буфера.

Как работает аллокатор в расте я признаюсь не в курсе, однако если я обнаружу что конструкция "массив из 4х int" занимает 20kb я сочту это ошибкой. То же относится к вопросу с resize — я не знаю поддерживает ли растовский аллокатор resize в принципе, и какие минусы при downsize, они могут быть.


Но зачем? Сэкономить 8 байт на стеке? Ценой копирования вектора?
Предположим у вас идёт накопление результатов, уже при сотне чисел в векторе вы можете как серьёзно сэкономить на ресайзах, так и неплохо сэкономить память конвертировав его в структуру минимального необходимого размера после того как сбор данных закончен.

Ну и по определению инварианта [T] он занимает памяти сколько минимально нужно, а не сколько попало, так что разрушать инвариант потому что можно в одном случае немного быстрее его получить смысла нет.

Мне кажется подобное поведение в принципе в расте сделать можно, но в конкретном случае из за динамической природы vec оно практически бесполезно — практически всегда размер вектора не совпадает с размером его буфера.
std::Vec всё еще умеет делать shrink_to_fit(). Экономия получается ровно в размер одного указателя, т.е. 8 байт на большинстве систем. Это сравнительно мало, когда речь идет о ручке, держащей в куче M байт с погрешностью > 8.
То же относится к вопросу с resize — я не знаю поддерживает ли растовский аллокатор resize в принципе, и какие минусы при downsize, они могут быть.
ну вот я и не могу понять почему в rust'овом Vec не используется realloc. В с++ проблема в нетривиальных типах — не зная заранее сработает realloc in-place или нет, написать корректное расширение/сжатие вектора через realloc попросту не получится. А проверять заранее будет сравнимо по стоимости с самой аллокацией, плюс придется реализовывать, ломать бинарную совместимость… Но в rust в Vec в принципе можно запихать только объекты тривиально муваемых типов, поэтому расширение/сжатие через realloc всегда должно быть корректным, независимо от того, сработало оно in-place или нет. Ну а бинарная совместимость при статической сборке мало кого волнует.

Так он и делает shrink_to_fit


let mut vec = Vec::with_capacity(100000);
vec.extend([1, 2, 3].iter().cloned());
assert_eq!(vec.len(), 3);
let slice = vec.into_boxed_slice();
assert_eq!(slice.len(), 3);

кажется вопрос что он должен делать shrink_to_fit закрыт.


Почему shrink_to_fit копирует а не делает realloc — полез я проверять это утверждение, и нашёл что он делает shrink https://doc.rust-lang.org/beta/src/alloc/raw_vec.rs.html#463, а как именно shrink выполняется — уже на совести выбранного аллокатора

Так он и делает shrink_to_fit
кажется вопрос что он должен делать shrink_to_fit закрыт.
этот вопрос и не поднимался. Я поднимал вопрос «зачем вообще нужен Vec::to_boxed_slice если это почти то же самое что и просто вызвать Vec::shrink_to_fit, с разницей в 8 байт и потерю функций вектора»
> у вас проблемы надуманные
В своей статье я дал ссылку на другую статью
habr.com/ru/post/460831
где обсуждаются теже самые проблемы с конструторами и приводятся теже самые выводы. Почему там проблемы не надуманные, а здесь уже надуманные?
Хотя в той статье я также вижу ваш комментарий, который если я правильно его интерпретировал, критикует данный подход
> В итоге оказывается, что побеждать надо было не конструктор, а плохую сигнатуру.
В случае с функцией/статическим методом этой функции можно дать любое релевантное имя. Как дать любое имя конструктору?
> который по большому счету будет делать плюс минус то же самое что и rust'овский mem::forget
Цель не в том, чтобы повторить mem::forget. Цель в том, чтобы не считать деструктор ВСЕГДА выполняющимся до конца.
В своей статье я дал ссылку на другую статью
habr.com/ru/post/460831
где обсуждаются теже самые проблемы с конструторами и приводятся теже самые выводы.
Это такое признание в плагиате?
Почему там проблемы не надуманные, а здесь уже надуманные?
не помню чтобы я говорил что по всем пунктам согласен с автором той статьи… Нет, согласен конечно с некоторыми аргументами, например про незавершенное состояние объекта внутри конструктора и про вызовы виртуальных методов из него. Но у вас такого нет. Однако у вас есть пункт про то, что вернуть ошибку из конструктора можно лишь исключением. Нюанс в том, что автор той статьи это проблемой не считает,
цитата
Это часто используется в качестве аргумента в пользу того, что использовать C++ без исключений сложно, и что использование конструкторов вынуждает также использовать исключения. Однако, я не думаю, что этот аргумент корректен: фабричные методы решают обе эти проблемы, потому что они могут иметь произвольные имена и возвращать произвольные типы
Ровно как он и не считает конструкторы чем-то универсально плохим, чему и посвящен последний абзац статьи. Более того, там автор преследует цель защитить/оправдать подход rust, а не просто вбросить на с++, что сделали вы.

И уже помимо этого, сразу следом идут откровенно нелогичные выпады, например «о при этом, выбросив из конструктора исключения, вы должны помнить, что деструктор данного элемента вызван не будет.» — почему вы вообще решили что деструктор не созданного объекта должен вызываться?
В случае с функцией/статическим методом этой функции можно дать любое релевантное имя. Как дать любое имя конструктору?
Например так
constexpr struct FromXmlTag {} from_xml;
constexpr struct FromJsonTag {} from_json;

class A {
public:
    A(FromXmlTag, std::string& s);
    A(FromJsonTag, std::string& s);
};

// usage:
A x(from_json, text);
A y(from_xml, text);
Цель не в том, чтобы повторить mem::forget. Цель в том, чтобы не считать деструктор ВСЕГДА выполняющимся до конца.
Во-первых, зачем такое может понадобиться? Во-вторых, если вам так нравится раст, покажите мне в сигнатуре трейта drop (что является максимально близким эквивалентом плюсовому деструктору) способ вернуть ошибку.

Извините, что я не знаю как вам объяснить необходимость свойства непрерывности у деструктора. Проблема в том, что я считаю это логически очевидным. Возможно, вам просто не стоит пытаться запихивать в деструкторы прерываемые операции?

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

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

Больше смахивает на попытку не использовать то, в чем не смог разобраться, чем на аргументацию
А можете пояснить, в чем например я не смог разобраться?

Скажите пожалуйста, вашу статью следует воспринимать всерьез? Выглядит она как своеобразная сатира. Вот и вынужден задать столь тупой вопрос, поскольку не могу понять, как же к ней относиться.

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

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

С кортежами вы, конечно, перемудрили.


1) Кортежи, идентифицирующие поля по типам, не взаимозаменяемы с кортежами, идентифицирующими поля по индексам.
Что вы хотели сказать, что "если требования изменятся… чуть подправив код".
Не чуть. Вам логику программы придётся переделать. Это как множества заменить на векторы, или наоборот.


2) Сам код класса Tuple — оверхед на оверхеде. Цену кортежа функций себе представляете?
А ведь, судя по описанию, всего-то и хотелось — сделать неявное отображение произвольных кортежей друг на друга (на кортеж с более узким набором типов, естественно).


3) Сделать диагностику ошибок компиляции более вменяемой можно с помощью static_assert'ов. Да, это отдельный труд. И, фактически, это входит в интерфейс библиотеке — не API, а метаинтерфейс, для компилятора и программиста.
Точно так же, как диагностика ошибок времени исполнения может варьироваться от UB (в доках было сказано "не делайте так", вы сделали, так вам и надо!) до логгирования, продуманных систем исключений / кодов возврата и точек отладки.


Вы жалуетесь на то, что вам было трудно проделать эту работу как автору библиотеки, а потом было больно, как клиенту библиотеки? Или что?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории