Silver 10 декабря 2019

ТОП 10 трюков на C++, которые облегчат вам жизнь

Мы подобрали приёмы, которые сделают программирование на С++ легче или, как минимум, интереснее. Освежите свои знания!
ТОП 10 трюков на C++, которые облегчат вам жизнь

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

Автовыведение типа

Несмотря на то, что ключевое слово auto было введено еще в C++ 11, многие программисты продолжают его игнорировать. А ведь автовыведение позволяет экономить время и делает код лаконичным.

Увидеть преимущества можно даже на примере стандартных типов STL:

        //создадим простенький вектор с координатами городов
std::map<std::string, std::pair<float, float>> coordinatesRus;
std::map<std::string, std::pair<float, float>> coordinatesUsa;

coordinatesRus["Moscow"] = std::make_pair(45.2376, 37.2236);
coordinatesRus["Sankt Petersburg"] = std::make_pair(59.9386, 30.3141);
coordinatesUsa["New York"] = std::make_pair(40.7142, -74.0059);
coordinatesUsa["Chikago"] = std::make_pair(41.8500, -87.6500);

std::vector<std::map<std::string, std::pair<float, float>>> coordinatesWorld;
coordinatesWorld.push_back(coordinatesRus);
coordinatesWorld.push_back(coordinatesUsa);
//теперь сделаем обход с двумя разными for

for (std::map<std::string, std::pair<float, float>> country : coordinatesWorld) {
	for (std::map<std::string, std::pair<float, float>>::iterator it = country.begin(); it != country.end(); ++it) {
		std::cout << it->first << " " << it->second.first << " " << it->second.second << std::endl;
	}
}

//и с auto

for (auto country : coordinatesWorld) {
    for (auto it = country.begin(); it != country.end(); ++it) {
	std::cout << it->first << " " << it->second.first << " " << it->second.second << std::endl;
    }
}
    

Сложный расчет констант

Использование констант – хороший тон. Это позволяет компилятору лучше оптимизировать код и делает его более явным. Но если вычисление слишком громоздкое, от модификатора const приходится отказываться. На помощь приходят лямбда-функции:

        const int myVariable = [&] {
	if (contidionA)
		return conditionB ? computeFunc(param) : 0;
	else
		return param * 2;
}();
    

Трюки с битовыми операциями

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

        //изящное определение четности
if ((n & 1) != 0) {
        //нечётный
}
else {
	//чётный
}

//свап значений x и y без создания третьей переменной при помощи ксора
x ^= y;
y ^= x;
x ^= y;

//умножение и деление на при помощи битового сдвига
y = x << 2; //x будет умножен на 4
y = x >> 3; //x будет разделен на 8
 


    

Трюки с логарифмом

Функцию log тоже можно использовать для ряда изящных решений:

        //определение старшей цифры числа n, если его разрядность неизвестна
double k = log10(n);
k = k - floor(k);
int result = pow(10, k);

//подсчет количества цифр в целом числе без цикла
int digitСount = floor(log10(n)) + 1
    

Встроенные алгоритмы

Удобно использовать встроенные функции all_of, any_of и none_of для быстрой проверки элементов коллекции на соответствие условию.

        //стандартный массив
if (std::all_of(arr, arr + 5, [](int elem) { return elem > 0; })) 
	std::cout << "Все элементы массива положительны" << std::endl;
//работа с коллекциями	 
if (std::any_of(someVector.begin(), someVector.end(), [](int elem) { return elem == 0; }))
	std::cout << "В векторе присутствует 0" << std::endl;
    

Трюки с шаблонами

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

Проверка специализации типа

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

        //проверка на пустые параметры
template <template <typename...> class T, typename U>
struct is_specialization_of : std::false_type {};

//создаем шаблон, принимающий в виде аргументов другой шаблон и шаблонный тип данных с неопределенным числом аргументов 
template <template <typename...> class T, typename... Us>
struct is_specialization_of<T, T<Us...>> : std::true_type {};

int main(){
    //тестируем
    std::cout << is_specialization_of<std::tuple, std::tuple<int>>::value;            
    std::cout << is_specialization_of<std::tuple, std::map<int,std::string>>::value;  

}
    

Проверка возможности конструирования элемента с набором конкретных параметров

Когда шаблонный класс задан в виде template<class T, typename... Args>, бывает трудно понять, какие аргументы можно использовать. Метод is_constructible из библиотеки type_traits даёт неполный ответ: он показывает, существует ли конструктор под конкретные аргументы. Для более полной картины можно использовать еще один шаблон:

        template <typename...>
struct identity {};

//проверка на пустые параметры
template <typename, typename, typename = std::void_t<>>
struct is_braces_constructible_impl : std::false_type {};

//определяем соответствие типов
template <typename T, typename... Args>
struct is_braces_constructible_impl < T, identity<Args...>,
	std::void_t<decltype(T{ std::declval<Args>()... }) >> : std::true_type {};
template <typename T, typename... Args>
struct is_braces_constructible : is_braces_constructible_impl<T, identity<Args...>> {};

int main(){
        std::cout << is_braces_constructible<std::tuple<int, int>, int, int>::value;
	std::cout << is_braces_constructible<std::tuple<int, int, int>, int, int>::value;
}
    

Вызов функции через кортеж

Креативное применение шаблонов, которое позволяет вызвать любую функцию, передав в неё параметры в виде кортежа:

        //основной шаблон, позволяющий вызвать функцию по ссылке и передать в неё кортеж
template <typename F, size_t... Idx, typename... Ts>
constexpr auto call_via_tuple_impl(F f, std::index_sequence<Idx...>, const std::tuple<Ts...>& args) {
	return f(std::get<Idx>(args)...);
}
//дополнительный шаблон, позволяющий не заботиться о размере кортежа
template <typename F, typename... Ts>
constexpr auto call_via_tuple(F f, const std::tuple<Ts...>& args) {
	return call_via_tuple_impl(f, std::make_index_sequence<sizeof...(Ts)>(), args);
}

int main(){
//создаем кортеж
constexpr auto tup = std::make_tuple(6, 33, 1);
//тестируем
auto func = [](int a, int b, int c) -> auto { return a + b + c; }; //можно сделать ссылку на функцию с неопределенным числом параметров
std::cout << call_via_tuple(func, tup);
}
    

Поиск первого элемента указанного типа в кортеже

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

        template <typename T, std::size_t N, typename... Ts>
struct index_of;

//проходим кортеж и определяем типы данных
template <typename T, std::size_t N, typename... Ts>
struct index_of<T, N, T, Ts...> : std::integral_constant<int, N> {}

template <typename T, std::size_t N, typename U, typename... Ts>
struct index_of<T, N, U, Ts...> : std::integral_constant<int, index_of<T, N + 1, Ts...>::value> {};template <typename T, std::size_t N>
struct index_of<T, N> : std::integral_constant<int, -1> {};

//находим первые совпадения и возвращаем
template <typename T, typename... Ts>
constexpr T get_first(const std::tuple<Ts...>& tup) {
	return std::move(std::get<index_of<T, 0, Ts...>::value>(tup));
}

int main(){
constexpr auto tup = std::make_tuple('x', 10, 'y', 1.0f);
std::cout << get_first<int>(tup);
std::cout << get_first<char>(tup);
std::cout << get_first<float>(tup);
}
    

Знаете другие трюки для C++? Делитесь ими в комментариях!

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ