All streams
Search
Write a publication
Pull to refresh
19
0
Евгений Неручек @reficul0

Разработка по ту сторону кроличьей норы

Send message

Пожалуйста, ознакомьтесь с https://stackoverflow.com/questions/23777863/do-rvo-and-copy-elision-only-work-within-one-compilation-unit-or-not.
RVO возможна только в одной единице трансляции (если брать в расчёт специальные опции линковщика). Поэтому, в общем случае, RVO и NRVO при пересечении бинарной границы между разными модулями - невозможны.

Кажется это противоречит совету использовать unique_ptr

Чтобы прояснить ситуацию с умными указателями, пожалуйста, ознакомьтесь с пунктом 2.2 Конструирование объекта

Никакого "барьера оптимизации"

Можете, пожалуйста, пояснить более подробно?
Я исхожу из того, что вызов функции make_person происходит из другого программного модуля, из-за чего copy elision - невозможно.

Если делать взызов этой функции из того же модуля, то и RVO и NRVO, конечно же, сработает, что верно иллюстрирует ваш пример из godbolt.

Почему не происходит?

Спасибо за статью! Понимаю, что статья - для новичков. Но, говоря про подводные камни распараллеливания, хотелось бы, чтобы тема так же была рассмотрена с точки зрения инвалидации хешей, false cache sharing, и как с этим бороться.

Спасибо вам за уточнения и проявленный к статье интерес!

По-видимому, считается, что такой конструктор заменяет конструктор по умолчанию

Можете, пожалуйста, уточнить какая формулировка из п. 3.7 натолкнула на такую мысль?

Спасибо за обратную связь!

Я очень подозреваю UB с dangling reference в специализации X::X<int>(int &&), потому что в списке инициализации X(int &&l) : val(l) член класса val (lvalue ссылка) ссылается на локальную переменную l

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

  1. Теоретические методы завязаны анализе и синтезе стандарта и расплывчатой формулировки, от которой зависит истинность: the initializer expression is used to initialize the destination object

  2. Эмпирический подход даёт разные результаты на разных компиляторах (+ вариациях оптимизаций), на основе которых сложно делать однозначные выводы.

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

Поэтому я рекомендую интерпретировать такой код (и его вариации), как ведущий в висячим ссылкам и UB, и выпиливать его при встрече.

Спасибо, что помогаете улучшить статью! Действительно, чтобы пример заработал придётся создавать массив немного хитрее (cppinsights):

template<typename T>
using dummy = T;

int main()
{
    int i = 1;
    auto&& a = dummy<int[3]>{1, 2, 3}[i];
}

Рекомендация не использовать такой трюк из п. 3.9 в таком свете становится ещё более актуальной.

Всегда можно реализовать CLI обёртку, которая будет проксировать вызовы C# <-> C++. И тогда можно будет часть функционала писать на С++, а часть на C#, пользуясь преимуществами обоих языков и при этом не переписывая всё с нуля.

Пример: часто видел как делают UI на WPF, а бизнес логику на C++.

Можно попросить какой-то реальный пример?

В общем случае подойдет любая задача, в рамках которой:

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

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

осталось впечатление, что решение задачи - БД, а вот такая структура данных - какой-то странный велосипед.

В частности подойдут задачи, в рамках которых функционала обычного STL-ного контейнера уже не хватает, но трудозатратно пересесть сразу на БД.
Или же если требуется что-то вроде in-memory БД, но с функционалом, который покрывает boost::multi_index::multi_index_container(т.е. как минимум без транзакционной модели из коробки).

Если говорить конкретнее, то лично мне данный контейнер пригодился в реализации:

  1. Биржевого стакана(в оперативном контейнере заявок и в снапшоте текущего состояния стакана).

  2. Фильтрации tcp пакетов по нескольким признакам.

  3. Фильтрации записей при парсе файлов разных форматов.

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

Способ индексации динамический с точки зрения изменения требований, а не с точки зрения runtime исполнения кода программы. То есть в данной статье не рассматривается механизм наиболее гибкого способа выпускать и применять патчи к программе(имею ввиду ситуацию, в которой новые требования к индексации появляются и должны быть учтены в уже выпущенной версии программы, в частности во время её исполнения на машине).

Возможно я неверно понял вашу проблему про линейный поиск, но предположу, что речь про то, что не получается придумать как искать элементы в вашем самопальном индексе быстрее. Конкретно насчёт вашей реализации, в ней каждый 'индекс', это аналог ordered_unique индекса из Boost.MultiIndex. Хоть и используется std::multiset, но специфика его элементов(указателей на значения из другого контейнера) такова, что два указателя на один и тот же объект точно не попадут в этот контейнер. Поэтому, если использовать std::multiset<T>::find то можно добиться логарифмического времени поиска.

Если же хочется реализовать аналоги для всех видов индексов, то для каждого из индексов в статье я привёл в аналогию STL-ный контейнер, и именно при помощи указанного контейнера можно реализовать аналог того или иного индекса.

Но в любом случае, ограничения по скорости поиска элемента по индексу всегда будут ограничены внутренним устройством аналогичного STL-ного контейнера. Поэтому, если вы хотите более быстрого поиска, то возможно вам нужен аналог hashed_unique индекса на основе std::unordered_set, с его средним константным временем поиска (чем меньше коллизий, тем вероятнее оно будет таким).

В принципе, для себя можно было бы обойтись подобием в виде нескольких unordered_map

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

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

В сравнении производительности, предоставленном в документации Boost.MultiIndex, авторы утверждают, что multi_index_container превосходит как по пространственной, так и по временной эффективности эквивалентные структуры данных, полученные через ручное комбинирование STL контейнеров. Это становится более заметно, когда количество индексов увеличивается.

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

В сравнении производительности, предоставленном в документации Boost.MultiIndex, авторы утверждают, что multi_index_container превосходит как по пространственной, так и по временной эффективности эквивалентные структуры данных, полученные через ручное комбинирование STL контейнеров. Это становится более заметно, когда количество индексов увеличивается.

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

Как оказалось, я действительно неверно понял механизм работы std::set<T>::is_transparent. Прошу прощения. Таким решением нельзя решить проблему [4](индексации по другим полям).

transparent компаратор позволяет сравнивать только по тому полю структуры, которое используется для определения отношения порядка в контейнере. В нашем случае, в методе operator()(Person const &, Person const &), это поле name. То есть is_transparent не решает проблему поиска по нескольким полям, а только позволяет выполнять поиск по значению, не создавая промежуточного пустого объекта Person (решает проблему [1]).

Если же говорить о том, что произойдёт если попробовать сделать индексацию по нескольким полям в transparent компараторе, то @0o00oo00o0 выяснил эмпирически, что поиск по контейнеру с таким компаратором ведёт себя нестабильно и может привести к падению:

mscv2019 программа падает, если заменить в find(41) из примера значение 41 на 40 или 60.


Поэтому решение на основе transparent компаратора становится не таким привлекательным.

Как оказалось, я неверно понял механизм работы std::set<T>::is_transparent. Прошу прощения. Таким решением нельзя решить проблему [4](индексации по другим полям).

transparent компаратор позволяет сравнивать только по тому полю структуры, которое используется для определения отношения порядка в контейнере. В нашем случае, в методе operator()(Person const &, Person const &), это поле name. То есть is_transparent не решает проблему поиска по нескольким полям, а только позволяет выполнять поиск по значению, не создавая промежуточного пустого объекта Person (решает проблему [1]).

Если же говорить о том, что произойдёт если попробовать сделать индексацию по нескольким полям в transparent компараторе, то @0o00oo00o0 выяснил эмпирически, что поиск по контейнеру с таким компаратором ведёт себя нестабильно и может привести к падению:

mscv2019 программа падает, если заменить в find(41) из примера значение 41 на 40 или 60.


Поэтому решение на основе transparent компаратора становится не таким привлекательным.

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

Да и даже если обозначенные проблемы решаются, то всё-равно частично.

Это действительно решает почти все обозначенные проблемы. Говорю почти все потому что, чтобы применить такое решение нужно:

  1. Использовать минимум C++14, если нужен гетерогенный поиск по упорядоченным контейнерам.

  2. Использовать минимум С++20, если нужен гетерогенный поиск по неупорядоченным контейнерам.

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

Ещё, если я вас верно понял, то стоит учитывать, что такое решение потребует написать намного больше кода и потребует больше ручной работы. Поскольку:

  1. Нужно будет определять по структуре тэга с конструктором, принимающим один параметр(если только это не POD), потому что нам надо будет передавать через объект этой тэга значение ключа.

  2. Нужно будет создавать реализацию operator() в компараторе для каждого из тегов вручную. При этом стоит учитывать, что всегда нужно по 2 реализации: когда тег - первый параметр, и когда второй.

В итоге придётся написать так:

struct PersonBySurname
{    
		PersonBySurname(std::string surname)        
			: surname(std::move(surname))    
		{
		}
		std::string surname;
};

class PersonsComparator
{
public:
    using is_transparent = void;
		
    bool operator()(Person const& lhs, Person const& rhs) const
    {
        return lhs.name < rhs.name;
    }
	
    bool operator()(PersonBySurname const& surnameTag, Person const& person) const
    {
        return surnameTag.surname < person.surname;
    }
    bool operator()(Person const& person, PersonBySurname const& surnameTag) const
    {
        return person.surname < surnameTag.surname;
    }
};

Вместо in-place объявления в типе контейнера boost::multi_index::tag<struct PersonsByName> :

boost::multi_index::hashed_unique<
		boost::multi_index::tag<struct PersonsBySurname>,
		boost::multi_index::member<Person, decltype(Person::surname), &Person::surname>
>

Масштаб этой проблемы незначителен на маленьком количестве тэгов. Но на маленьком наборе и не получится проверить насколько такое решение гибкое. Если добавить много разных тегов, в т.ч. и композитных, то даже на небольшом множестве из 3 полей может получится уже до 6 структур тегов и 12 реализаций operator() для каждой структуры, и весь этот код придётся писать вручную. Соответственно, в более сложных системах всё будет более печально.

Поэтому я не считаю такое решение достаточно гибким.

Information

Rating
Does not participate
Location
München, Bayern, Германия
Registered
Activity

Specialization

Software Developer, Application Developer
C#
C++
Multiple thread
C++ Boost
Git
High-loaded systems
Rust