Вопросы и ответы для собеседования и подготовки Senior C++ разработчика

Всем привет! Решил систематизировать и записать актуальные на 2025 год вопросы и ответы программисту в позиции Senior C++. Вы можете это использовать как для проведения собеседований в качестве интервьюера, так и для подготовки к ним. Считаю, это самая скучная статья на моём сайте, но лучший способ что-то запомнить — это записать и систематизировать свои знания.

Немного о себе: пишу на C++ уже восемнадцатый год и немножко понимаю в программировании.

Кстати, недавно узнал, что человек запоминает 25% любой информации, но надолго. И вот смотрю я на эту прорву текста и думаю — если это всего лишь четверть, то ни фига себе — насколько огромная часть знаний прошла мимо меня.

Статья создавалась постепенно: после каждого собеседования я дополнял её новыми вопросами, с которыми столкнулся лично. Например, перед началом написания статьи я ничего не знал про perfect forwarding (мельком слышал про универсальную ссылку) и std::shared_mutex.

Где располагаются переменные, чем инициализированы, область видимости:

int a;
static int b;
int c = 30;
const int d = 20; // без слова "extern" переменная d видна только в этом файле

void function() {
  int fa;
  int fb = 10;
  const int fc = 5;
  static int fd; // инициализируется нулём при первом вызове функции
  static int fe = 1; // инициализируется единичкой при первом вызове функции 
}

Если мы объявляем переменную в глобальном пространстве, то указание static делает видимость этой переменной только в этом файле. Указание константности приводит (в зависимости от желания компилятора) помещение его значения по умолчанию в секцию .rodata исполняемого файла или сразу встраивание этого значения в код вместо переменной. Все переменные объявленные вне функций инициализированы нулём по умолчанию и живут в сегменте памяти BSS, который выделяется и заполняется нулями при запуске программы, его размер записан в исполняемом файле. Не const переменные сохраняют своё значение в сегменте памяти DATA исполняемого файла. Этот сегмент копируется в память компьютера при запуске программы. Все глобальные переменные живут в статической памяти (BSS/DATA/.rodata). Статическая память выделятся при старте программы, имеет постоянный размер и существует в течение всей жизни программы. Размещение переменных по сегментам статической памяти — это поведение на этапе компоновки, описанное в стандартных соглашениях (Application Binary Interface, ABI). Сводная таблица всех переменных из примера выше:

Переменная Инициализация Сегмент памяти Видимость
int a; Неявная (0) BSS Глобальная
static int b; Неявная (0) BSS Файловая
int c = 30; Явная (30) DATA Глобальная
const int d = 20; Явная (20) .rodata или inline Файловая
int fa; Мусор стек Блочная
int fb = 10; Явная (10) стек Блочная
const int fc = 5; Явная (5) стек или inline Блочная
static int fd; Неявная (0) BSS Блочная
static int fe = 1; Явная (1) DATA Блочная

Правила вывода типа для auto

auto по умолчанию создает копию без ссылок и квалификаторов (const, volatile) верхнего уровня. auto сохраняет низкоуровневые квалификаторы (касающиеся указываемых данных), но отбрасывает высокоуровневые (касающиеся самой переменной-указателя).

Пример:

int x = 42;
const int *p1 = &x; // нельзя менять данные, но можно указатель
int *const p2 = &x; // нельзя менять указатель, но можно менять данные
const int *const p3 = &x; // нельзя менять указатель, нельзя менять данные
const int &p4 = x; // ссылка на const int 

auto a1 = p1; // const int * - оставили const для данных
auto a2 = p2; // int * - убрали const для указателя
auto a3 = p3; // const int * - оставили const для данных, убрали const для указателя
auto a4 = p4; // int - убрали const, убрали ссылку

В итоге, правила для auto:

  • указатели: сохраняют квалификаторы низкого уровня
  • ссылки: отбрасывает ссылку, отбрасывает const

Что такое RAII?

Resource Acquisition Is Initialization — дословно «захват ресурса есть инициализация». В конструкторе захватываем ресурс, в деструкторе освобождаем.

Какие бывают умные указатели?

  • std::shared_ptr — RAII указатель на объект со счётчиком ссылок. Копирование увеличивает счётчик ссылок, в деструкторе счётчик уменьшается. При достижении нуля освобождает память.
  • std::weak_ptr — слабая ссылка на объект, управляемый std::shared_ptr. Не увеличивает счётчик ссылок. Для использования нужно преобразовать в std::shared_ptr с помощью метода lock(). Полезен, чтобы избежать циклических ссылок между std::shared_ptr.
  • std::unique_ptr — указатель с исключительным владением объектом. Запрещено копирование, разрешено только перемещение. Для передачи владения используйте std::move. Имеет специализацию для массивов: std::unique_ptr<T[]>.

Почему лучше использовать std::make_shared и std::make_unique, когда возможно?

Во время вычисления параметров конструктора возможно исключение, которое приведёт к утечке памяти. Также std::make_shared выделяет память для объекта и контрольного блока за одну аллокацию. В конце концов, читать удобнее.

Что происходит при возникновении исключения.

Выполнение функции прерывается, идёт поиск подходящего catch. Сначала в текущей функции, потом идёт последовательно выход наверх по стеку. При выходе вызываются все деструкторы локальных объектов, которые покидают текущую область видимости. Если подходящий catch не найден, то std::terminate(). Если во время раскрутки стека вызовов происходит повторное исключение, то снова std::terminate().

Что происходит при возникновении исключения в конструкторе/деструкторе?

  • Если исключение происходит в конструкторе, для уже созданных членов класса вызываются деструкторы, деструктор этого объекта не вызывается, а дальше всё как обычно
  • Деструктор по умолчанию объявлен как noexcept со стандарта C++11, поэтому любое исключение, покидающее деструктор вызывает std::terminate(). Также может возникнуть ситуация, если при обработке исключения в деструкторе во время раскрутки стека вызовов произойдёт исключение — то будет std::terminate()

Почему память выделенная new/malloc будет утечкой, а умный указатель корректно освободит память при вызове исключения?

Потому что происходит раскрутка стека и очистка локальных переменных, выделенных на стеке, плюс вызываются деструкторы этих переменных. Сырой указатель от new/malloc не имеет деструктора и выделен в куче.

В чём особенность placement new?

При использовании placement new деструктор нужно вызывать вручную. Правильный порядок действий:

// 1. Выделили память
void* memory = ::operator new(sizeof(MyClass));

// 2. Создали объект (placement new)
MyClass* obj = new (memory) MyClass();

// 3. Использовали объект
obj->method();

// 4. Вызвали деструктор
obj->~MyClass();

// 5. Освободили память
::operator delete(memory);

Что такое срез исключения? Когда происходит? Как избежать?

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

try {
  throw MyException(what, extended_data); // наследник std::exception
}
catch (std::exception e) { // тут происходит СРЕЗ
  // Значение extended_data утеряно
}

Поэтому лучший путь — это перехватывать исключение по константной ссылке.

try {
  throw MyException(what, extended_data); // наследник std::exception
}
catch (const std::exception &e) {
  // Сохраняется полная информация об исключении
}

Что происходит при бросании исключения из noexcept метода?

Немедленный вызов std::terminate() и аварийное завершение программы.

Что происходит при вызове виртуального метода в конструкторе?

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

struct Base {
public:
    Base() {
       print(); // Вызывается Base::print()
    }

    virtual void print() { ... }
};

struct Derived : Base {
   void print() override { ... }
}

Что происходит при вызове виртуального метода в деструкторе?

То же самое, что в вопросе выше. Только в конструкторе производный класс ещё не построен, а в деструкторе производный класс уже разрушен.

Какие бывают касты в C++.

Целую заметку написал, не буду повторяться: Различные касты в C++.

Что такое семантика перемещения (move semantic)?

Механизм, который, начиная с C++11, позволяет эффективно передавать ресурсы между объектами без дорогостоящего копирования. Использование выглядит так:

std::vector<int> a;
a.resize(1000000); // выделяем много памяти
std::vector<int> b = std::move(a); // перемещаем данные, исходный объект в неопределённом состоянии

После выполнения кода объект b владеет данными, объект a находится в валидном, но неопределённом состоянии. Полезно знать, что std::move только преобразует тип в r-value ссылку и ничего никуда не перемещает, реальное перемещение выполняет конструктор перемещения или оператор перемещения.

Что такое RVO и NRVO

Это оптимизации компилятора под названием Copy elision (пропуск копий), которые позволяют избежать лишнего копирования/перемещения при возврате объектов из функций:

  • RVO(Return Value Optimization) — оптимизация безымянного возвращаемого значения, гарантировано стандартом C++17:
HeavyObject create() {
   return HeavyObject();
}
  • NRVO(Named Return Value Optimization) — оптимизация именованного возвращаемого значения, когда возвращается именованная локальная переменная. Обратите внимание — использование std::move при возврате значения ломает эту оптимизацию!
HeavyObject create() {
   HeavyObject a;
   return a;
}

Что такое и как использовать std::future/std::promise? В каких случаях std::future бросает ошибку?

future/promise — это механизм передачи данных или исключений между потоками. Одному std::promise соответствует один std::future.

  • std::promise — устанавливает значение (set_value()) или исключение (set_exception()), которое извлекается с помощью std::future.
  • std::future — предоставляет доступ к результату (get()), записанному через std::promise. Метод get() вызывает исключение, если std::promise уничтожен без установки значения или исключения. Повторный вызов get() также вызовет ошибку.

Пример для наглядности:

#include <iostream>
#include <future>
#include <thread>

int main() {
  auto worker = [](auto p) {
    try {     
        p.set_value("Hello world!");
        // Делаем что-то вызывающее исключение
    }
    catch(...) {
        // Передаём исключение
        p.set_exception(std::current_exception());
    }
  };

  std::promise<std::string> promise;
  auto f = promise.get_future();

  // Передаём promise по значению в поток с помощью перемещения
  std::thread t(worker, std::move(promise));

  try {
     std::cout << f.get() << std::endl;
  } 
  catch (const std::exception &e) {
     std::cerr << "Exception : " << e.what() << std::endl;
  }

  t.join();
}

std::future можно использовать для получения значения только один раз и нельзя копировать, если хотите это изменить, создайте std::shared_future с помощью метода std::future::share() — этот класс лишён таких недостатков.

Что такое deadlock и как происходит?

Блокирование потоков в ожидании друг друга. Происходит, когда в потоке A блокируется мьютекс a, потом b. А в потоке B сначала мьютекс b, потом a. Исправляется использованием std::scoped_lock или одинаковым порядком блокирования мьютексов.

Зачем нужен std::shared_mutex?

Вместе с std::shared_lock и std::unique_lock std::shared_mutex обеспечивает множественный доступ (на чтение) при использовании std::shared_lock и эксклюзивный доступ (на запись) при использовании std::unique_lock соответственно.

Пример для наглядности:

std::shared_mutex mutex;
std::vector<int> data;

void write(int value) { 
    std::unique_lock lock(mutex); // эксклюзивная блокировка
    data.push_back(value);
}

int read(size_t index) {
    std::shared_lock lock(mutex); // разделяемая блокировка
    return data.at(index);
}

Зачем нужно указание memory_order в объектах типа std::atomic? Какие бывают memory_order?

Если вкратце, то memory_order позволяет контролировать модель памяти для атомарных операций. Модель памяти определяет, как операции чтения и записи памяти могут быть переупорядочены компилятором и процессором для оптимизации. Начиная от самой слабой гарантии std::memory_order_relaxed и заканчивая самой строгой std::memory_order_seq_cst (по умолчанию). Если хотите подробнее вникнуть в тему, то вот сорокаминутное видео с объяснением от разработчика из Яндекса:

Чем процесс отличается от потока?

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

Что будет если вызвать new с огромным куском памяти?

Произойдёт вызов исключения std::bad_alloc, можно использовать new (std::nothrow) если хочется получить nullptr в случае неудачи.

Как работает алгоритм std::remove/std::remove_if

Пробегаемся по контейнеру слева направо двумя итераторами:

  • первый итератор (first) указывает на текущий элемент
  • второй (result) указывает на позицию куда сохранять подходящие элементы.

Реализация алгоритма для наглядности:

template<typename ForwardIt, typename UnaryPredicate>
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p) {
    ForwardIt result = first;
    for (; first != last; ++first) {
        if (!p(*first)) {
            if (result != first) {
                *result = std::move(*first);
            }
            ++result;
        }
    }
    return result;
}

Какие бывают контейнеры STL. Устройство и сложность операций поиска/вставки/удаления.

  • std::vector — выделяет непрерывный кусок памяти, если не хватает места, то выделяется бОльший кусок, старые данные копируются туда, доступ имеет сложность O(1), вставка в конец имеет амортизированную сложность O(1), вставка в начало/середину требует сдвига всех данных вправо и имеет сложность O(n). Эффективно использует кеш процессора. Имеет возможность доступа по индексу.
  • std::list — двусвязный список, поиск O(n), доступ по индексу O(n), вставка O(1) при наличии итератора, удаление O(1) при наличии итератора, доступ к элементам по итератору.
  • std::forward_list — односвязный список, аналогичен std::list, но занимает меньше памяти. Может вставлять/удалять только после известного элемента.
  • std::deque — двусторонняя очередь, реализуется как массив указателей на блоки, доступ O(1), вставка/удаление с начала/конца O(1), в середине O(n).
  • std::unordered_set/std::unordered_map — хеш-таблица из бакетов, поиск, вставка, удаление O(1) в среднем случае, O(n) в худшем (при коллизиях), элементы без сортировки.
  • std::set/std::map — красно-черные деревья, поиск/вставка/удаление O(log n). Элементы отсортированы.
  • std::array — аналог сишного массива фиксированного размера с плюшками STL в виде итераторов и деструктора
  • std::span — интерфейс для работы с непрерывными последовательностями данных без владения этими данными
  • std::string_view — аналог std::span для std::string, доступ к данным строки без владения

Что такое универсальная ссылка, что такое perfect forwarding?

Perfect forwarding — это способ передать аргумент в другую функцию точно так, как он был получен:

  • lvalue остаётся lvalue
  • rvalue остаётся rvalue
  • const остаётся const

Выглядит так:

template<typename T>
void foo(T&&value) // T&& является универсальной ссылкой
{
    bar(std::forward<T>(value));
}

Тут есть важный нюанс для шаблонных классов, легко ошибиться:

template<typename T>
class A {
    void foo(T&&value) // это не универсальная ссылка
    {
        bar(std::forward<T>(value));
    }

    template<typename U>
    void foo2(U&&value) // а здесь всё работает правильно 
    {
        bar(std::forward<U>(value));
    }
}

Требований для perfect forwarding два:

  1. Это должна быть шаблонная функция с универсальной ссылкой
  2. использование std::forward для передачи параметров дальше

Почему так сделано? Очевидно, потому, что создатели C++ ненавидят людей и желают им всего плохого, а если без шуток, то, чтобы избежать проблем при инстанцировании шаблона класса ну и щепотка ненависти к людям, конечно. Кстати T&& становится универсальной ссылкой, только в контексте вывода параметров шаблона.

P.S. Благодарю создателей Markdown за синтаксис языка разметки, всё форматирование сделано с помощью него.

Обновляем краткое содержания статей в блоге с помощью ИИ, денег, bash и wp-cli

Всем привет! Если вы не первый раз на моём сайте, то могли заметить, что краткое содержание заметок содержит первые n слов (вроде, 22) из заметки безо всякого осмысления. По крайней мере, так было до недавнего времени.

Поскольку сейчас наступил XXI век, то пора передать формирование краткого содержания заметок на откуп искусственному интеллекту.

Делать плагин мне откровенно не хочется, поэтому я обновил все заметки с помощью bash-скрипта, запрашивая краткое содержание статьи через curl у Yandex GPT через их API.

Алгоритм такой:

  • получаю ID всех статей блога с помощью wp-cli
  • для каждой статьи блога, отправляю её содержимое в Yandex GPT с помощью API
  • получаю краткую выжимку статьи и показываю пользователю
  • если он согласен с содержимым, то обновляю поле post_excerpt у статьи
  • вывожу post_excerpt в мета поле description при формировании статьи в разделе
  • ???
  • PROFIT

Для регистрации и работы с Yandex GPT нужно немного денег, я потратил 50 рублей. Регистрация и получение идентификаторов каталога (folder_id) и API ключа (api_key) найдёте в этой статье на «Хабре»: (Как подключить Yandex GPT к своему проекту на Python)[https://habr.com/ru/articles/780008/].

Добавил в скрипт подтверждение каждого шага от пользователя, потому что иногда ИИ выдаёт какую-то политкорректную дичь типа «Я не могу обсуждать эту тему. Давайте поговорим о чём-нибудь ещё» на простые запросы, а также он не умеет работать с видео. И добавил проверку, что цитата не заполнена для поста, чтобы лишний раз не тратить запросы к ИИ.

Перед запуском скрипта, не забудьте изменить folder_id, api_key и wordpress_dir на свои.

#!/usr/bin/env bash

# Конфигурация
FOLDER_ID="folder_id"
API_KEY="api_key"
API_URL="https://llm.api.cloud.yandex.net/foundationModels/v1/completion"

wrap_for_yandexgpt() {
  local text="$1"

  jq -n \
    --arg text "$text" \
    --arg folder "$FOLDER_ID" \
    '{
      "modelUri": "gpt://\($folder)/yandexgpt-lite",
      "completionOptions": {
        "stream": false,
        "temperature": 0.3,
        "maxTokens": "2000"
      },
    "messages": [
    {
      "role": "user",
      "text": $text
    }
  ]
}'
}

# Функция для отправки запроса
create_post_excerpt() {
  local content="$1"
  local prompt="Сделай краткое метаописание статьи (до 60 слов):\n\n$content"
  local json_payload
  json_payload=$(wrap_for_yandexgpt "$prompt")

  # Выполняем запрос и сохраняем ответ в переменную
  local response
  response=$(curl -s -w "\nHTTPSTATUS:%{http_code}" -X POST -H "Content-Type: application/json" -H "Authorization: Api-Key $API_KEY" -H "x-folder-id: $FOLDER_ID" -d "$json_payload" $API_URL)

  # Проверяем, что curl выполнился успешно
  if [ $? -ne 0 ]; then
    echo "Ошибка выполнения curl" >&2
    return 1
  fi

  # Извлекаем HTTP код и тело ответа
  local http_code
  local body
  http_code=$(echo "$response" | sed -n 's/HTTPSTATUS://p')
  body=$(echo "$response" | sed '$d')

  # Проверяем HTTP-код
  if [ "$http_code" -ne 200 ]; then
    echo "ошибка HTTP: $http_code" >&2
    return 1
  fi

  # Проверяем наличие ошибки в теле ответа
  if echo "$body" | jq -e '.error' >/dev/null 2>&1; then
    local error_msg
    error_msg=$(echo "$body" | jq -r '.error.message // .error')
    echo "ошибка API: $error_msg" >&2
    return 1
  fi  

  # Извлекаем текст
  local result
  result=$(echo "$body" | jq -r '.result.alternatives[0].message.text // empty')

  if [ -z "$result" ]; then
    echo "пустой ответ от API" >&2
    return 1
  fi

  local status
  status=$(echo "$body" | jq -r '.result.alternatives[0].status // empty')

  if [ "$status" == "ALTERNATIVE_STATUS_CONTENT_FILTER" ]; then
    echo "[cencored]"
    return 1
  fi

  echo "$result"
  return 0
}

update_post_excerpt() {
  local post_id=$1
  local excerpt="$2"
  wp --allow-root post update "$post_id" --post_excerpt="$excerpt"
}

get_post_excerpt() {
  local post_id=$1
  wp --allow-root post get "$post_id" --field=post_excerpt
}

get_post_title() {
  local posts_data="$1"
  local post_id="$2"

  echo "$posts_data" | jq ".[] | select(.ID==$post_id) | .post_title"
}

get_post_content() {
  local posts_data="$1"
  local post_id="$2"

  echo "$posts_data" | jq ".[] | select(.ID==$post_id) | .post_content"
}


cd wordpress_dir || exit

posts_data=$(wp post list --allow-root --post_type=post --fields=ID,post_title,post_content,post_excerpt --format=json --post_status=publish | jq 'map(select(.post_excerpt=="" or .post_excerpt==null))')

post_ids=$(echo "$posts_data" | jq .[].ID)

for post_id in $post_ids; do
  title=$(get_post_title "$posts_data" "$post_id")
  echo "ЗАГОЛОВОК: $title"
  content=$(get_post_content "$posts_data" "$post_id")
  post_excerpt=$(create_post_excerpt "$content")
  exit_code=$?

  if [ $exit_code -ne 0 ]; then
    echo "ОШИБКА ПОЛУЧЕНИЯ ЦИТАТЫ: $post_excerpt"
    echo
    continue
  fi

  echo "ЦИТАТА: \"$post_excerpt\""

  while true; do
    read -r -n1 -p "Обновить цитату? (y - да, n - пропустить, q - выход) " choice
    choice=$(echo "$choice" | tr '[:upper:]' '[:lower:]')
    echo

    case $choice in
      y)
        update_post_excerpt "$post_id" "$post_excerpt"
        break
        ;;
      n)
        break
        ;;
      q)
        exit 0
        ;;
      *)
        continue
        ;;
    esac
  done

  echo
done

cd - || exit

Всё, что делает этот скрипт он делает только под вашу личную ответственность. Я никаких гарантий не даю и ответственности за результат не несу. Примерно половину скрипта написал DeepSeek, претензии к нему.

Добавляем подсветку нового языка в плагин Prismatic+Prism.js

Всем привет! Понадобилось мне вставить CMakeLists.txt в мою заметку с подсветкой кода. В блоге она включена с помощью плагина Prismatic и библиотеки Prism.js c темой «Tomorow Night», если вам интересно. И так родилась эта инструкция по добавлению нового языка (в нашем случае CMake) в плагин. Все работы проходят в папке плагина wp-content/plugins/prismatic, порядок действий следующий:

  1. Переходим на страницу скачивания Prism. Выбираем minified версию и только тот язык, который вам нужен, у меня был язык cmake и получился файл на 18 килобайт.
  2. Вставляем по адресу ./lib/prism/js/lang-cmake.js кусок кода с Prism.language.cmake. Пример lang-cmake.js
  3. Открываем ./inc/resources-enqueue.php в функции prismatic_prism_classes в первом массиве после 'language-bash' вставляем 'language-cmake', во втором после 'lang-bash' вставляем 'lang-cmake'.

Мне кажется, первый массив отвечает за название CSS класса, а второй — название JS файла, но я не уверен, потому что я ненастоящий PHP программист.

Подключение git репозитория в CMake

Всем привет, недавно изучал либу SFML и с удивлением обнаружил, что её можно подключить в CMakeLists.txt с помощью команды FetchContent следующим образом:

cmake_minimum_required(VERSION 3.28)

project(main LANGUAGES CXX)

include(FetchContent)
FetchContent_Declare(SFML
    GIT_REPOSITORY https://github.com/SFML/SFML.git
    GIT_TAG 3.0.2
    GIT_SHALLOW ON
    EXCLUDE_FROM_ALL
    SYSTEM)
FetchContent_MakeAvailable(SFML)

add_executable(main src/main.cpp)
target_compile_features(main PRIVATE cxx_std_17)
target_link_libraries(main PRIVATE SFML::Graphics)

И при создании файлов для сборки, CMake самостоятельно скачает и подключит SFML к вашему проекту — чистая магия, на мой взгляд.

Текирова, Кемер, Турция — поездка на неделю

Писать или нет?, — думал я. По дороге в аэропорт провожающий гид «обрадовал», что рейс будет «Анталия-Махачкала», а не «Анталия-Петербург». И это ещё везение — кому-то до нас достался Новосибирск или Самара. «Точно писать!» — это сигнал свыше. Махачкала — это как будто Россия, но Дагестан, а достаточна ли у меня длина рукавов и штанин для посещения данной местности — я не очень уверен.

Овчарка Белла

Итак, мы купили относительно недорогой тур в трехзвездочный отель Турции на семь дней. Что сказать, отель так себе, но радушие хозяев и местная живность в лице немецкой овчарки Беллы, рыжего кота, желтоглазой кошки и четырёх озорных котят подкупают, поэтому о минусах отеля писать не буду. Мы славно ели, крепко спали, подкармливали зверушек на завтрак и ужин. В отеле у нас всё прошло хорошо.

Полосатый котёнок

От отеля до пляжа примерно километр пути, лежаки бесплатно предоставляет местное кафе при условии покупки у них чего-нибудь, но это неточно — иногда мы занимали лежаки просто так.

Жили мы в городке Текирова, почти на краю Кемерской губернии и на экскурсии нас забирали первыми и возвращали последними (иногда наоборот). Из-за этой особенности водителям было лениво отвозить нас так далеко от Анталии, и они дважды пытались «сбагрить» нас товарищам, которым было по пути. Один раз нам удалось от такой пересадки отбиться, в другой — нет.

Ощущения, когда ты последний пассажир ночью в автобусе с незнакомыми турецкими мужиками — незабываемое. Особенно они обостряются после рассказа о беженцах-наркоманах из Сирии которые могут и ножом пырнуть, плюс ИГИЛ — всё ещё действующая организация, до которой ехать километров пятьсот.

По пути в аэропорт, к счастью, ситуация поменялась и рейс снова стал Анталия — Санкт-Петербург и все радостно захлопали, так что в Махачкалу, хвала Аллаху, попасть не суждено. Потому что специально в Дагестан я никогда бы не поехал.

Итак, насчёт экскурсий раздумья такие: больше их покупать не будем, арендуем машину/мопед и прокатимся по Кемерскому краю самостоятельно. Кстати, вот вам полезный совет: открываете сайт с названием местного тур оператора (Marş Travel, Şereşe Travel, Ginza Travel и т.д.) и требуете, чтобы экскурсию вам продали по интернет цене, иначе продаван называет цену, какая придёт в его светлую темноволосую голову. Мы были на экскурсии, где цена варьировалась от 25 до 40 баксов за человека, причём состав и условия экскурсий был одинаковыми. Мы купили несколько поездок: хамам, Демре-Мира-Кекова, бухта Порто Дженевиз, развалины города Перге, теперь по порядку о каждой поездке без прикрас.

Котёнок на дереве

Хамам

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

Демре Мира Кекова

Посещение трёх городов и церкви Николая Чудотворца, также известного как Санта Клаус.

Оказалось, Санта не живёт с женой на Северном Полюсе, а давно умер, и власти скрывают! Перед покупкой экскурсии вам льют в уши, что вы поплывёте на судне со стеклянным дном обозревать руины древних городов, по факту получаются два грязных окошка, в которые ничего не видно кроме зелёной воды. Глумиться про религию не буду (это не одобряют жена и УК РФ), но давно я не видел такого коммерциализированного подхода к религии, особенно в сфере продаж реквизита. Из хорошего — посещение церкви Святого Николая Чудотворца. Монументальное и очень древнее строение. Удивительно, как в начале нашей эры смогли возвести подобное сооружение.

Бухта Порто Дженевиз

Прогулка на кораблике с купанием по трём бухтам: два раза по часу и один на полчаса. Старт корабля из Адрасана. Рекомендую вместо этого купить экскурсию на турецкие Мальдивы — Сулуада. Там вода чище, виды красивее и в целом лучше. Экскурсия запомнилась знакомством с байкерами из Нижнего Новгорода, которые своим ходом через Грузию за шесть дней доехали до Кемера. Из неприятного — тётка в маршрутке, которая заняла чужое место и внезапно забыла русский язык. Так мы и познакомились с байкерами.

Отдельно хочу отметить Адрасан — уникальное место, где кораблики припаркованы кормой прямо к пляжу, вереница кораблей километра два длиной. Вид грандиозный. Если там не были, обязательно побывайте.

Перге

Четыре события по цене одного и пятое в подарок. Сначала везут в парк к верхнему Дюденскому водопаду. Потом часик гуляем по развалинам древнего города Перге. Потом — ужин вкуснейшими котлетками-кюфте, много-много пахлавы, турецкий чаёк. Далее отвозят в «Land of legends» с шоу фонтанов, красивым замком Nickelodeon и элитными магазинами. В завершение программы — вишенка на торте: пересадка посреди ночи из комфортного автобуса в ссаную маршрутку с двумя неизвестными турками, потому что водителю лениво далеко ехать, зато бакшиш брать не лениво.

Замок Nickelodeon

Кемер

Три года назад мы были в Кемере и решили его посетить самостоятельно. Ездили в самую жару на автобусе номер 8, если правильно помню. Вспомнили, что там очень неудобный заход в море через гальку и на пляже не очень-то полежишь. Так что мы были в Кемере один раз. Рядом, кстати, располагается античный ликийский город Фаселис, но туда мы не добрались.

Статуя коня в Кемере

В целом это небольшое путешествие очень понравилось и оставило самые приятные воспоминания — когда ещё понежишься на солнце, когда дома 16 градусов «тепла» и дождь. Это был не очень логичный поступок, когда мы с женой одновременно безработные, но считаю все было не зря, зимой будет что вспомнить.

Блог Евгения Жирнова