February 5, 2020

10 особенностей С++, о которых знают лишь опытные программисты

Многие функции реализованы в С++, однако не обо всех знают простые программисты. 10 особенностей языка, которые помогут повысить ваш уровень мастерства.

1. Условные операторы

Большая часть разработчиков, работающих с С++, знают и используют условные операторы.

x = (y < 0) ? 10 : 20;

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

(a == 0 ? a : b) = 1;

Что является сокращением следующего:

if (a == 0) a = 1; else b = 1;

Стоит применять с осторожностью.

2. URI в С++

У вас есть возможность поместить URI в исходники C++ без получения ошибок. Например:

void foo() { 
    http://stackoverflow.com/ 
    int bar = 4; 
    ...
 }

3. Арифметика указателей

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

4. Шаблоны и функции

Многие возможности языка можно реализовать только благодаря различным сторонним библиотекам.

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

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

Самая известная из функций С++ - метапрограммирование шаблонов. С ее помощью код либо частями, либо полностью выполнится за время компилирования, а не во время выполнения. Но для того, чтобы работать с ней, вам потребуются значительные знания шаблонов проектирования.

Иные варианты использования множественной парадигмы позволяют создавать способы программирования вне языка С - предка С++:

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

Команда: этот шаблон проектирования позволяет отложить выполнение кода. Большинство других шаблонов позволят легко и эффективно реализовать стили программирования, не входящие в список “официальных парадигм С++”. Кроме того, их использование улучшит типобезопасность (благодаря автоматизированным malloc/realloc/free).

Объекты в C++ действительно обладают достаточным функционалом, неправильное использование которого приведет к большим трудностям, но даже динамический полиморфизм имеет свою статическую версию: CRTP.

Наиболее предпочтительный способ добавить объекту функции, который при этом будет в духе объектно-ориентированного программирования - сделать это с помощью non-member non-friend функции, а не просто функции-члена. Да, волосы многих Java-разработчиков сейчас встали дыбом, но это действительно так. А все потому что:

  • В С++ как функции-члены, так и non-member функции находятся в одном пространстве имен.
  • функции-члены non-member функции не обладают привилегированным доступом к внутренностям класса. Из этого выходит, что использование non-member non-friend функции функцией-членом ослабит инкапсуляцию класса.

5. Псевдоним для namespace alias

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

namespace fs = boost::filesystem; 
fs::path myPath( strPath, fs::native );

6. Объявление классов и функций

В init части цикла for можно объявлять не одни лишь переменные. Здесь также можно разместить классы и функции.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) { ... }

Благодаря этому можно использовать множество переменных с разными типами.

7. Ассоциативность оператора массива

A[8] - это то же самое, что и *(A + 8). Так как операция сложения ассоциативна, она может быть переписана как *(8 + A), что синонимично ..... 8[A]
Никто не говорил, что это должно быть обязательно полезно... :-)

8. Объединения тоже могут быть шаблонами

Еще одна вещь, о которой знают немногие, это то, что объединения тоже могут быть шаблонами:

template
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

У них также могут быть конструкторы и функции-члены. Ничего общего с наследованием(включая виртуальные функции).

9. Метапрограммирование

C++ - это язык программирования, в котором существует несколько парадигм. Они позволяют расширить функционал языка. Так, например, в нем существует метапрограммирование шаблонов. Вероятно, никто не ожидал, что оно позволит стать языку Тьюринг-полным подъязыком, способным исполняться во время компиляции.

10. Унарный оператор +

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

Перевод Enumeration в integer
+AnEnumeratorValue

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

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

struct Foo { 
  static int const value = 42; 
  }; 
// Пытается делать что-то интересное... 
template
void f(T const&);

int main() {
// не получилось связать- пробуем получить адрес 
  f(Foo::value);

// работает - передает временное значение 
 f(+Foo::value);
}

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

// Это делает кое-что интересное... 
template void f(T const& a, T const& b); 
int main() { 
int a[2]; 
int b[3]; 
f(a, b); // не работает! разные значения для "T"! 
f(+a, +b); // работает! T это "int*" в обоих случаях }