Pull to refresh

Comments 26

>В реальной библиотеке все немного сложнее, потому что стандартом зафиксировано, что std::move имеет два шаблонных параметра. Если программист явно их укажет, то все равно надо выбрать оптимизированный вариант. Поэтому различные реализации описаны под служебными именами, а в самом std::move находится код, выбирающий наиболее подходящую реализацию. Вот значительно упрощенный вариант:

Вы, кажется, здесь заговорились. Move-семантика и сопутствующий std::move здесь не при чем, речь же идет об std::copy.
Кроме того, в последнем исходнике данное место, на мой взгляд, некорректно:
// Выбираем подходящую реализацию
    return __do_copy::do_copy(first, last, result);

Поскольку вы не передали шаблонный параметр is_simple, когда запросили специализацию __do_copy.
Move-семантика и сопутствующий std::move здесь не при чем, речь же идет об std::copy.

Да, ошибся. Так как std::move и std::copy похожи, то в GNU STL они реализованы как одно целое и используют общие части реализации. Из-за этого понять, как оно работает еще труднее.
Ничего подобного. У std::move свой прототип, и никаких общих частей c std::copy:

template< class T >
typename std::remove_reference<T>::type&& inline move( T&& t ) {
  return t;
}


Весь смысл — гарантированно добавить два амперсанда к типу выражения.
Это другой std::move, он от одного аргумента. А еще есть std::move и std::move_backward от трёх аргументов: start, finish и result, которые перемещают range объектов. И для тривиально копруемых типов у них такие же реализации, как и у std::copy. Так что вы погорячились.
Ваш std::move в , а наш — в . Последний использует первый в наиболее общей реализации при прохождении цикла.
Парсер съел меня имена хэдеров: utility и algorithm.
Сейчас набежит кучка любитилей экономить на спичках и будут с пеной у рта утверждать, что если сделать собственную коллекцию с жесткой типизацией, то она будет работать в разы быстрее без всяких трюков, потому выводы статьи не актуальны и «лучше» делать велосипеды на каждый случай :(
Ведь действительно, если у нас есть std::hash_map с именами файлов в качестве ключей, то можно придумать в разы более эффективную коллекцию и хэш функцию. И тогда будет в цикле 100000 поисков выполняться не 600мс, а 400мс.
Оптимизации на уровне библиотеки не менее важны, чем сбор спичек в отдельных задачах, поскольку представляют из себя приятный бонус в любом проекте, использующем эти библиотечные вызовы. Кроме того, оптимизация может повлиять и опосредовано, если вы пересоберете сторонний компонент (за что мы любим opensource), который используете у себя, а он, негодяй этакий, к std::copy внутри обращается. Разве это не прекрасно?
Я говорю как раз об обратной крайности — когда люди оптимизируют собственные проекты и изобретают свои контейнеры/логеры/лямбды, потому что «библиотечное слишком generic и дает гибкость в ущерб скорости». Так вот это утверждение как раз в корне неверно, что нам и показал автор статьи и за что ему отдельное спасибо.
Да нет, оно бывает вполне себе верным, коли не до всего руки у авторов STL дотягиваются. В той же Visual Studio 2010 std::copy для POD структур в контейнере std::vector вызовется поэлементное копирование вместо блочного. Это может стоить дорого.
Сложный вопрос. Приведу пример. Есть весьма неплохой фреймворк под названием QT. Появился он на заре становления C++, когда STL еще не устаканился и разные реализации были не совсем совместимы друг с другом. Поэтому в QT решили сделать свои контейнеры: vector, map и т.д. Сейчас это мешается, потому что в программе приходится работать и с STL и с QT контейнерами. А код описывающий бизнес-логику оказывается зависим от библиотеки GUI. Кроме того, теперь команде QT приходится поддерживтаь эти контейнеры, добавлять в них поддержку списков инициализации и т.п.

Иногда писать велосипеды приходится, но при этом у автора должно быть четкое, подтвержденное тестами, представление, какую выгоду принесет велосипед и во сколько обойдется его поддержка в будущем. В том числе нужно учитывать потребности, дикутемые предметной областью.
s/QT/Qt, прежде всего.

Контейнеры в Qt ныне несут другие цели, я просто процитирую:
>The QTL has a different philosophy from the STL, which is well summarized by J. Blanchette: «Whereas STL's containers are optimized for raw speed, Qt's container classes have been carefully designed to provide convenience, minimal memory usage, and minimal code expansion.»

Сам я не разделяю эту точку зрения в полной мере, примеры слету:
— «Странный» итератор у QMap, коорый несовместим с оным из std::map (i.key() вместо i.first).
— Отсутствие метода assign в том же QVector
— Невозможность удалить диапазон в QList

и т.п.
И это будут именно «любители», потому что профессионалы знают, что сначала нужно использовать стандартные коллекции и алгоритмы — это быстрее и надежнее, а вот когда мы уже с их использованием упрёмся в нехватку 200мс — тогда можно и на свои переходить. А в 95% случаев — не упрёмся.
И это, пусть и классическая, но самая верная точка зрения.
Есть std::unordered_map. std::hash_map в стандарте нет. Если не ошибаюсь, он был доступен в реализации от SGI, потом в старых версиях компилятора от MS.
И у него один из шаблонных параметров — class Hash = std::hash<Key>, как раз для того, чтобы можно было под конкретную задачу подобрать хэш функцию.
Вы не сможете выбрать способ разрешения коллизий: метод цепочек или открытую адресацию. Это далеко не последнее свойство, влияющее на поведение хеш-таблицы.
UFO just landed and posted this here
Собственно, я так и не понял зачем SFINAE. можно просто с type traits:

template<class T>
struct RelocateByMemcpy
{
	static const bool allow = is_trivial<T>::value;
};

template <class T>
void Relocate(T* newLocation, T* oldLocation)
{
	if(newLocation == oldLocation)
		return;

	if(RelocateByMemcpy<T>::allow)
	{
		memcpy(newLocation, oldLocation, sizeof(T));
	}
	else
	{
		new(newLocation) T(*oldLocation);
		oldLocation->~T();
	}
};

И для разных T можно переопределять allow c помощью частичной специализации шаблона.

P. S. Это перенос одного элемента, тут можно memcpy.
Можно и так. Такой подход тоже широко применяется и в STL тоже. Тут вы воспользовались тем, что компилятор достаточно умный и понимает, что в условный оператор передана константа. Одну ветку он просто выкидывает и не тратит время на сравнение. Я хотел написать про него, но решил не раздувать статью.

Конкретно для std::copy есть еще тонкость, что std::move для тривиально копируемых типов использует те же самые реализации. Поэтому их в любом случае нужно было выносить в отдельную функцию.
if можно вообще исключить, достаточно сделать перегрузку дополнительным параметром bool_type, например.

overload1 (param1, param2, true_type);
overload2 (param1, param2, false_type);

В этом случае компилятор почти наверное выкинет в телах overloadX неиспользуемый третий параметр, в то же время, можно пометить эти функции как inline, что приводит нас к гарантированному отсутствию накладных расходов.
А это уже очень близко к тому, как работает enable_if. Только там дополнительный тип вынесен вхитрое описание возвращаемого типа. Дело в том, что компилятор не имеет права просто так выкинуть неиспользуемый третий параметр. Он входит в сигнатуру функции и её будут вызывать, указывая его. Но, если тело функции будет заинлайнено, то руки у компилятора развязываются и он действительно может его выкинуть.
enable_if меньше полагается на особенности компилятора и поэтому лучше.
А вот enable_if вовсе не всегда делает ситуацию ортогональной.
Попробуйте с помощью enable_if провернуть такой трюк с шаблонным конструктором класса (выбирать специализацию). Вам неизбежно придется использовать искусственный параметр.
Согласен. Для конструкторов придётся использовать дополнительный параметр. Так и документация на enable_if говорит:
en.cppreference.com/w/cpp/types/enable_if
Там же есть применение для классов, через дополнительный параметр шаблона.

В таком случае enable_if позволяет объявить true_type из вашего способа прямо в месте использования.
Можно, в MSVC так и делают, насколько я читал исходники и слушал St. Lavavej. Они называют это type erasure, когда внутри функции, которую надо специализировать, с помощью трейтов выбирается нужная перегрузка.
Sign up to leave a comment.

Articles

Change theme settings