﻿# Анализ кода проекта DeepSpeech или почему не стоит писать в namespace std

DeepSpeech – это открытый и свободно распространяемый движок распознавания речи, разрабатываемый компанией Mozilla\. Движок имеет довольно высокую производительность и хорошие отзывы пользователей, и это делает код проекта интересной мишенью для проверки\. Данная статья посвящена разбору ошибок, найденных в C\+\+\-коде проекта DeepSpeech\.

![0768_DeepSpeech_ru/image1.png](https://import.viva64.com/docx/blog/0768_DeepSpeech_ru/image1.png)

## Введение

Мы уже неоднократно искали ошибки в проектах, использующих машинное обучение, и [DeepSpeech](https://github.com/mozilla/DeepSpeech) не стал для нас исключением\. Не удивительно, ведь этот проект является достаточно популярным: на момент написания статьи он имеет уже более 15k звёзд на GitHub\.

Как обычно, поиск ошибок, которые я приведу в этой статье, проводился с помощью статического анализатора кода PVS\-Studio\.

Для своей работы DeepSpeech использует библиотеку TensorFlow\. Я отключил анализ кода этой библиотеки, потому что про неё мы уже писали [отдельную статью](https://pvs-studio.ru/ru/blog/posts/cpp/0497/), однако я не стал отключать анализ остальных используемых библиотек\. С чем это связано? Ошибки внутри любой библиотеки, которую вы включаете в ваш проект, также становятся и ошибками внутри вашего проекта\. Поэтому полезно проводить анализ не только вашего, но и любого стороннего кода, который вы используете\. Подробное мнение об этом вы можете прочитать [в нашей недавней статье](https://pvs-studio.ru/ru/blog/posts/cpp/0762/)\.

На этом краткое вступление заканчивается – пора переходить к разбору ошибок\. Кстати, если вы зашли сюда, чтобы узнать ответ на вопрос, заданный мной в заголовке статьи \(почему не стоит писать в namespace std\) – можете сразу заглянуть в конец статьи\. Там вас ждёт по\-особенному интересный пример\!

## Обзор 10 интересных предупреждений, выданных анализатором

**Предупреждение 1**

[V773](https://pvs-studio.ru/ru/docs/warnings/v773/) The function was exited without releasing the 'data' pointer\. A memory leak is possible\. edit\-fst\.h 311

```cpp
// EditFstData method implementations: just the Read method.
template <typename A, typename WrappedFstT, typename MutableFstT>
EditFstData<A, WrappedFstT, MutableFstT> *
EditFstData<A, WrappedFstT, MutableFstT>::Read(std::istream &strm,
                                               const FstReadOptions &opts)
{
  auto *data = new EditFstData<A, WrappedFstT, MutableFstT>();
  // next read in MutabelFstT machine that stores edits
  FstReadOptions edits_opts(opts);

  ....
  
  std::unique_ptr<MutableFstT> edits(MutableFstT::Read(strm, edits_opts));
  if (!edits) return nullptr; // <=

  ....
}
```

Данный фрагмент кода содержит классический пример утечки памяти: функция _Read_ вызывает '_return nullptr_', не освободив при этом память, выделенную с помощью выражения '_new EditFstData_'\. При подобном выходе из функции \(без вызова _delete data_\) будет удалён только сам указатель, а деструктор объекта, на который он указывает, вызван не будет\. Таким образом, объект продолжит храниться в памяти, а удалить или использовать его уже будет нельзя\. 

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

**Предупреждение 2**

[V1062](https://pvs-studio.ru/ru/docs/warnings/v1062/) The 'DfsState' class defines a custom 'new' operator\. The 'delete' operator must also be defined\. dfs\-visit\.h 62

```cpp
// An FST state's DFS stack state.
template <class FST>
struct DfsState {
public:
  ....
  void *operator new(size_t size, 
                     MemoryPool<DfsState<FST>> *pool) {
    return pool->Allocate();
  }
  ....
}
```

PVS\-Studio не стоит на месте и продолжает пополняться новыми диагностиками\. Данный фрагмент кода – отличный пример для того, чтобы показать на нём работу свежей диагностики под номером [V1062](https://pvs-studio.ru/ru/docs/warnings/v1062/)\.

Правило, за выполнением которого следит эта диагностика, просто: если вы определяете собственный оператор 'new', то вы также должны определить и собственный оператор 'delete'\. Аналогично работает и наоборот: если вы определяете собственный 'delete', то и собственный 'new' тоже надо определить\.

В примере выше данное правило нарушено: создаваться объект будет с помощью определенного нами 'new', а удаляться – стандартным 'delete'\. Давайте посмотрим, что же делает функция _Allocate_ класса _MemoryPool_, которую вызывает наш собственный 'new':

```cpp
void *Allocate() {
  if (free_list_ == nullptr) {
    auto *link = static_cast<Link *>(mem_arena_.Allocate(1));
    link->next = nullptr;
    return link;
  } else {
    auto *link = free_list_;
    free_list_ = link->next;
    return link;
  }
}
```

Эта функция создаёт элемент и добавляет его в связный список\. Логично, что такую аллокацию следовало прописать в собственном 'new'\. 

Но постойте\-ка\! Прямо несколькими строками ниже содержится вот такая функция:

```cpp
void Free(void *ptr) {
  if (ptr) {
    auto *link = static_cast<Link *>(ptr);
    link->next = free_list_;
    free_list_ = link;
  }
}
```

Значит, у нас уже есть готовые функции как для аллокации, так и для освобождения\. Скорее всего программист должен был написать собственный оператор 'delete', использующий для освобождения именно функцию _Free\(\)_\.

Анализатор обнаружил еще как минимум три таких ошибки:

* [V1062](https://pvs-studio.ru/ru/docs/warnings/v1062/) The 'VectorState' class defines a custom 'new' operator\. The 'delete' operator must also be defined\. vector\-fst\.h 31
* [V1062](https://pvs-studio.ru/ru/docs/warnings/v1062/) The 'CacheState' class defines a custom 'new' operator\. The 'delete' operator must also be defined\. cache\.h 65

**Предупреждение 3**

[V703](https://pvs-studio.ru/ru/docs/warnings/v703/) It is odd that the 'first\_path' field in derived class 'ShortestPathOptions' overwrites field in base class 'ShortestDistanceOptions'\. Check lines: shortest\-path\.h:35, shortest\-distance\.h:34\. shortest\-path\.h 35

```cpp
// Base class
template <class Arc, class Queue, class ArcFilter>
struct ShortestDistanceOptions {
  Queue *state_queue;    // Queue discipline used; owned by caller.
  ArcFilter arc_filter;  // Arc filter (e.g., limit to only epsilon graph).
  StateId source;        // If kNoStateId, use the FST's initial state.
  float delta;           // Determines the degree of convergence required
  bool first_path;       // For a semiring with the path property (o.w.
                         // undefined), compute the shortest-distances along
                         // along the first path to a final state found
                         // by the algorithm. That path is the shortest-path
                         // only if the FST has a unique final state (or all
                         // the final states have the same final weight), the
                         // queue discipline is shortest-first and all the
                         // weights in the FST are between One() and Zero()
                         // according to NaturalLess.

  ShortestDistanceOptions(Queue *state_queue, ArcFilter arc_filter,
                          StateId source = kNoStateId,
                          float delta = kShortestDelta)
      : state_queue(state_queue),
        arc_filter(arc_filter),
        source(source),
        delta(delta),
        first_path(false) {}
};
// Derived class
template <class Arc, class Queue, class ArcFilter>
struct ShortestPathOptions
    : public ShortestDistanceOptions<Arc, Queue, ArcFilter> {
  using StateId = typename Arc::StateId;
  using Weight = typename Arc::Weight;

  int32 nshortest;    // Returns n-shortest paths.
  bool unique;        // Only returns paths with distinct input strings.
  bool has_distance;  // Distance vector already contains the
                      // shortest distance from the initial state.
  bool first_path;    // Single shortest path stops after finding the first
                      // path to a final state; that path is the shortest path
                      // only when:
                      // (1) using the ShortestFirstQueue with all the weights
                      // in the FST being between One() and Zero() according to
                      // NaturalLess or when
                      // (2) using the NaturalAStarQueue with an admissible
                      // and consistent estimate.
  Weight weight_threshold;  // Pruning weight threshold.
  StateId state_threshold;  // Pruning state threshold.

  ShortestPathOptions(Queue *queue, ArcFilter filter, int32 nshortest = 1,
                      bool unique = false, bool has_distance = false,
                      float delta = kShortestDelta, bool first_path = false,
                      Weight weight_threshold = Weight::Zero(),
                      StateId state_threshold = kNoStateId)
      : ShortestDistanceOptions<Arc, Queue, ArcFilter>(queue, filter,
                                                       kNoStateId, delta),
        nshortest(nshortest),
        unique(unique),
        has_distance(has_distance),
        first_path(first_path),
        weight_threshold(std::move(weight_threshold)),
        state_threshold(state_threshold) {}
};
```

Согласитесь, не так\-то просто найти потенциальную ошибку, верно?

Проблема кроется в том, что и базовый, и производный класс содержат поля с одинаковыми именами: _first\_path_\. Это приведет к тому, что в производном классе будет находиться собственное, другое поле, которое своим именем перекрывает поле из базового класса\. Такие ошибки могут привести к серьезной путанице\.

Чтобы лучше понять, что я имею ввиду, предлагаю рассмотреть краткий синтетический пример из нашей документации\. Допустим, мы имеем следующий код:

```cpp
class U {
public:
  int x;
};

class V : public U {
public:
  int x;  // <= V703 here
  int z;
};
```

Здесь имя _x_ перекрыто внутри производного класса\. Теперь вопрос: какое значение выведет следующий код?

```cpp
int main() {
  V vClass;
  vClass.x = 1;
  U *uClassPtr = &vClass;
  std::cout << uClassPtr->x << std::endl;
  ....
}
```

Если вы считаете, что будет выведено неопределённое значение, то вы правы\. В данном примере запись единицы будет произведена в поле производного класса, а вот чтение будет производиться из поля базового класса, которое на момент вывода всё еще осталось неопределённым\.

Перекрытие имен в иерархии классов – это потенциальная ошибка, которую лучше не допускать :\)

**Предупреждение 4**

[V1004](https://pvs-studio.ru/ru/docs/warnings/v1004/) The 'aiter' pointer was used unsafely after it was verified against nullptr\. Check lines: 107, 119\. visit\.h 119

```cpp
template <....>
void Visit(....)
{
  ....
  // Deletes arc iterator if done.
  auto *aiter = arc_iterator[state];
  if ((aiter && aiter->Done()) || !visit) {
    Destroy(aiter, &aiter_pool);
    arc_iterator[state] = nullptr;
    state_status[state] |= kArcIterDone;
  }
  // Dequeues state and marks black if done.
  if (state_status[state] & kArcIterDone) {
    queue->Dequeue();
    visitor->FinishState(state);
    state_status[state] = kBlackState;
    continue;
  }
  const auto &arc = aiter->Value();       // <=
  ....
}
```

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

В таком случае давайте проследим, что случится с _aiter_, если он действительно будет равен нулю\. Сначала этот указатель будет проверен в выражении '_if \(\(aiter && aiter\-\>Done\(\)\) \|\| \!visit\)_'\. Это условие будет равняться _false_, и в then\-ветвь этого _if_ мы не попадём\. А дальше, по всем канонам классических ошибок, произойдет разыменование нулевого указателя: '_aiter\-\>Value\(\);_'\. Результатом такого разыменования является неопределённое поведение\.

**Предупреждение 5**

Следующий пример содержит сразу две ошибки:

* [V595](https://pvs-studio.ru/ru/docs/warnings/v595/) The 'istrm' pointer was utilized before it was verified against nullptr\. Check lines: 60, 61\. mapped\-file\.cc 60
* [V595](https://pvs-studio.ru/ru/docs/warnings/v595/) The 'istrm' pointer was utilized before it was verified against nullptr\. Check lines: 39, 61\. mapped\-file\.cc 39

```cpp
MappedFile *MappedFile::Map(std::istream *istrm, bool memorymap,
                            const string &source, size_t size) {
  const auto spos = istrm->tellg();        // <=
  ....
  istrm->seekg(pos + size, std::ios::beg); // <=
  if (istrm) {                             // <=
    VLOG(1) << "mmap'ed region of " << size
            << " at offset " << pos
            << " from " << source
            << " to addr " << map;
  return mmf.release();
  }
  ....
}
```

Ошибка, найденная здесь, более понятна, чем ошибка из предыдущего примера\. Указатель _istrm_ сначала разыменовывается \(два раза\), а только после этого следует проверка на ноль и логирование ошибки\. Это явно указывает: если в эту функцию в качестве _istrm_ придет нулевой указатель, то неопределённое поведение \(или, что более вероятно, падение программы\) произойдёт без всякого логирования\. Непорядок\.\.\. такие баги не стоит пропускать\. 

![0768_DeepSpeech_ru/image2.png](https://import.viva64.com/docx/blog/0768_DeepSpeech_ru/image2.png)

**Предупреждение 6**

[V730](https://pvs-studio.ru/ru/docs/warnings/v730/) Not all members of a class are initialized inside the constructor\. Consider inspecting: stones\_written\_\. ersatz\_progress\.cc 14

```cpp
ErsatzProgress::ErsatzProgress()
  : current_(0)
  , next_(std::numeric_limits<uint64_t>::max())
  , complete_(next_)
  , out_(NULL)
{}
```

Анализатор предупреждает нас, что конструктор инициализирует не все поля структуры _ErzatzProgress_\. Давайте сравним этот конструктор со списком полей этой структуры:

```cpp
class ErsatzProgress {
  ....
private:
    void Milestone();

    uint64_t current_, next_, complete_;
    unsigned char stones_written_;
    std::ostream *out_;
};
```

Действительно, можно заметить, что конструктор инициализирует все поля, кроме _stones\_written\__\.

_Примечание_: данный пример может и не являться ошибкой\. Настоящая ошибка произойдет лишь тогда, когда значение неинициализированного поля будет _использовано_\. 

Тем не менее, диагностика [V730](https://pvs-studio.ru/ru/docs/warnings/v730/) помогает заблаговременно отлаживать случаи такого использования\. Ведь возникает закономерный вопрос: если программист решил специально проинициализировать все поля класса, то почему у него должна быть причина оставить одно поле без значения?

Мои догадки насчет того, что поле _stones\_written\__ не было инициализировано по ошибке, подтвердились, когда несколькими строками ниже я увидел еще один конструктор:

```cpp
ErsatzProgress::ErsatzProgress(uint64_t complete,
                               std::ostream *to,
                               const std::string &message)
  : current_(0)
  , next_(complete / kWidth)
  , complete_(complete)
  , stones_written_(0)
  , out_(to)
{
  ....
}
```

Здесь проинициализированы все поля класса, что подтверждает: программист действительно планировал проинициализировать все поля, но случайно забыл про одно\.

**Предупреждение 7**

[V780](https://pvs-studio.ru/ru/docs/warnings/v780/) The object '& params' of a non\-passive \(non\-PDS\) type cannot be initialized using the memset function\. binary\_format\.cc 261

```cpp
/* Not the best numbering system,
   but it grew this way for historical reasons
 * and I want to preserve existing binary files. */
typedef enum
{
  PROBING=0,
  REST_PROBING=1,
  TRIE=2,
  QUANT_TRIE=3,
  ARRAY_TRIE=4,
  QUANT_ARRAY_TRIE=5
}
ModelType;

....

struct FixedWidthParameters {
  unsigned char order;
  float probing_multiplier;
  // What type of model is this?
  ModelType model_type;
  // Does the end of the file 
  // have the actual strings in the vocabulary?
  bool has_vocabulary;
  unsigned int search_version;
};

....

// Parameters stored in the header of a binary file.
struct Parameters {
  FixedWidthParameters fixed;
  std::vector<uint64_t> counts;
};

....

void BinaryFormat::FinishFile(....)
{
  ....
  // header and vocab share the same mmap.
  Parameters params = Parameters();
  memset(&params, 0, sizeof(Parameters)); // <=
  ....
}
```

Чтобы разобраться в этом предупреждении, предлагаю сначала разобраться, что такое PDS\-тип\. Аббревиатура "PDS" означает "Passive Data Structure" – простая структура данных\. Иногда вместо "PDS" говорят "POD" – "Plain Old Data"\. Говоря простым языком \(цитирую с русской [Википедии](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85)\), PDS\-тип – это тип данных, имеющий жёстко определённое расположение полей в памяти, не требующий ограничения доступа и автоматического управления\. Говоря еще проще, это тип данных, состоящий только из встроенных типов\.

Отличительной особенностью POD\-типов является то, что переменные этих типов можно изменять и обрабатывать с помощью примитивных функций управления памятью \(memset, memcpy и так далее\)\. Однако про "не\-PDS" типы такое сказать нельзя: столь низкоуровневое обращение с их значениями может приводить к серьезным ошибкам\. Например, к утечкам памяти, двойному очищению одного и того же ресурса или к неопределённому поведению\.

На код, приведенный выше, PVS\-Studio выдаёт предупреждение: так обращаться со структурой типа _Parameters_ нельзя\. Если заглянуть в определение этой структуры, можно увидеть, что второй её член имеет тип _std::vector_\. Этот тип активно использует автоматическое управление памятью и помимо содержимых данных хранит в себе дополнительные, служебные переменные\. Зануление такого поля с помощью _memset_ может нарушить логику работы класса и является серьезной ошибкой\.

**Предупреждение 8**

[V575](https://pvs-studio.ru/ru/docs/warnings/v575/) The potential null pointer is passed into 'memcpy' function\. Inspect the first argument\. Check lines: 73, 68\. modelstate\.cc 73

```cpp
Metadata*
ModelState::decode_metadata(const DecoderState& state, 
                            size_t num_results)
{
  ....
  Metadata* ret = (Metadata*)malloc(sizeof(Metadata));
  ....
  memcpy(ret, &metadata, sizeof(Metadata));
  return ret;
}
```

Следующее предупреждение говорит нам о том, что в функцию _memcpy_ передаётся нулевой указатель\. Да, действительно, если функция _malloc_ не сможет произвести аллокацию памяти, то она вернёт _NULL_\. В этом случае этот указатель будет передан в функцию _memset_, где произойдёт его разыменование – и, соответственно, фееричное падение программы\.

Однако, некоторые наши читатели могут возмутиться: если память переполнилась/фрагментировалась настолько, что _malloc_ не смог выделить память, то не всё ли равно, что произойдет дальше? Программа так и так упадёт, ведь из\-за отсутствия памяти она не сможет функционировать нормально\. 

Мы неоднократно сталкивались с таким мнением и считаем, что оно неверно\. Я бы рассказал вам детально, почему это действительно так, но эта тема заслуживает отдельной статьи\. Настолько заслуживает, что мы написали её еще несколько лет назад :\) Если вам интересно, почему стоит всегда проверять указатель, возвращенный функциями типа _malloc_, то приглашаю вас к прочтению: [Почему важно проверять, что вернула функция malloc](https://pvs-studio.ru/ru/blog/posts/cpp/0558/)\.

**Предупреждение 9**

Следующее предупреждение вызвано теми же причинами, что и предыдущее\. Правда, указывает оно немного на другую ошибку\.

[V769](https://pvs-studio.ru/ru/docs/warnings/v769/) The 'middle\_begin\_' pointer in the 'middle\_begin\_ \+ \(counts\.size\(\) \- 2\)' expression could be nullptr\. In such case, resulting value will be senseless and it should not be used\. Check lines: 553, 552\. search\_trie\.cc 553

```cpp
template <class Quant, class Bhiksha> class TrieSearch {
....
private:
  ....
  Middle *middle_begin_, *middle_end_;
  ....
};

template <class Quant, class Bhiksha>
uint8_t *TrieSearch<Quant, Bhiksha>::SetupMemory(....)
{
  ....
  middle_begin_
    = static_cast<Middle*>(malloc(sizeof(Middle) * (counts.size() - 2)));
  middle_end_ = middle_begin_ + (counts.size() - 2);
  ....
}
```

Так же, как и в предыдущем примере, здесь происходит аллокация памяти с помощью функции _malloc_\. Возвращенный при этом указатель без всякой проверки на _nullptr_ используется в арифметическом выражении\. Увы, результат такого выражения не будет иметь никакого смысла, и в поле _middle\_end\__ будет храниться совершенно бесполезное значение\.

**Предупреждение 10**

Ну и, наконец, самый интересный на мой взгляд пример был найден в библиотеке kenlm, включенной в DeepSpeech:  

[V1061](https://pvs-studio.ru/ru/docs/warnings/v1061/) Extending the 'std' namespace may result in undefined behavior\. sized\_iterator\.hh 210

```cpp
// Dirty hack because g++ 4.6 at least wants
// to do a bunch of copy operations.
namespace std {
inline void iter_swap(util::SizedIterator first,
                      util::SizedIterator second)
{
  util::swap(*first, *second);
}
} // namespace std
```

Приём, названный в комментарии "грязный трюк" действительно является грязным\. Дело в том, что подобное расширение пространства имен _std_ может привести к неопределённому поведению\. 

Почему? Потому что содержимое пространства имён _std_ определяется исключительно комитетом стандартизации\. Именно поэтому международный стандарт языка C\+\+ явно запрещает расширять _std_ подобным образом\. 

Последним стандартом, поддерживаемым в g\+\+ 4\.6, является C\+\+03\. Приведу переведённую цитату из [C\+\+03 final working draft](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf) \(см\. пункт 17\.6\.4\.2\.1\): "Поведение C\+\+\-программы не определено, если она добавляет объявления или определения в пространство имён std или пространство имён, вложенное в std, за исключением случаев, для которых указано обратное"\. Эта цитата актуальна для всех последующих стандартов \(C\+\+11, C\+\+14, C\+\+17, и C\+\+20\)\.

Предлагаю рассмотреть, как можно было исправить проблемный код из нашего примера\. Первый логичный вопрос: а что это за "случаи, для которых указано обратное"? Ситуаций, при которых расширение _std_ не ведет к неопределённому поведению, несколько\. Про все эти ситуации вы можете прочитать подробнее на странице [документации к диагностике V1061](https://pvs-studio.ru/ru/docs/warnings/v1061/), но сейчас для нас важно, что одним из таких случаев является добавление специализации шаблона функции\.

Т\.к\. _std_ уже имеет функцию под названием _iter\_swap_ \(отмечу: функцию\-шаблон\), логично предположить, что программист желал расширить её возможности так, чтобы она смогла работать с типом _util::SizedIterator_\. Только вот незадача: вместо того, чтобы добавить _специализацию шаблона функции_, программист просто написал _обыкновенную перегрузку_\. Следовало написать так:

```cpp
namespace std {
template <>
inline void iter_swap(util::SizedIterator first,
                      util::SizedIterator second)
{
  util::swap(*first, *second);
}
} // namespace std
```

Однако, с этим кодом тоже всё не так просто\. Дело в том, что данный код будет корректен только до стандарта C\+\+20\. Да\-да, специализации шаблонов функций в нём тоже отметили как приводящие к неопределённому поведению \(см\. [C\+\+20 final working draft](https://isocpp.org/files/papers/N4860.pdf), п\. 16\.5\.4\.2\.1\)\. А поскольку данный код принадлежит библиотеке, вполне вероятно, что рано или поздно её соберут с флагом _\-std\=C\+\+20_\. Кстати, PVS\-Studio различает, какая версия стандарта используется в коде, и в зависимости от этого выдает или не выдаёт предупреждение\. Убедитесь сами: [пример для C\+\+17](https://godbolt.org/z/ez3v6K), [пример для C\+\+20](https://godbolt.org/z/4P8EoG)\. 

На самом деле можно поступить гораздо проще\. Для того, чтобы исправить ошибку, достаточно лишь перенести собственное определение _iter\_swap_ в тот же namespace, в котором определён класс _SizedIterator_\. При этом в местах, где вызывается _iter\_swap_, нужно дописать "using std::iter\_swap;"\. Получится так \(определение класса _SizedIterator_ и функции _util::swap\(\)_ изменены для простоты\):

```cpp
namespace util
{
  class SizedIterator
  {
  public:
    SizedIterator(int i) : m_data(i) {}

    int& operator*()
    {
      return m_data;
    }
  private:
    int m_data;
  };

  ....

  inline void iter_swap(SizedIterator first,
                        SizedIterator second)
  {
    std::cout << "we are inside util::iter_swap" << std::endl;
    swap(*first, *second);
  }
}


int main()
{
  double d1 = 1.1, d2 = 2.2;
  double *pd1 = &d1, *pd2 = &d2;
  util::SizedIterator si1(42), si2(43);

  using std::iter_swap;

  iter_swap(pd1, pd2);
  iter_swap(si1, si2); // "we are inside util::iter_swap"

  return 0;
}
```

Теперь компилятор самостоятельно выберет нужную перегрузку функции _iter\_swap_ на основе поиска с учётом аргументов \(ADL\)\. Для класса _SizedIterator_ будет вызываться именно версия из _namespace util_, а для остальных типов будет вызываться версия из _namespace std_\. Доказательства [по ссылке](https://godbolt.org/z/dqd97q)\. Более того, внутри библиотечных функций не нужно дописывать никаких "using": поскольку их код и так находится внутри _std_, компилятор всё равно будет выбирать правильную перегрузку\.

И тогда – вуаля – пользовательская функция _iter\_swap_ будет работать как надо без всяких "грязных трюков" и прочего колдовства :\)

![0768_DeepSpeech_ru/image3.png](https://import.viva64.com/docx/blog/0768_DeepSpeech_ru/image3.png)

## Заключение

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

Если вы разрабатываете на C, C\+\+, C\# или Java и если вам, как и мне, интересна тема статического анализа, то предлагаю вам попробовать PVS\-Studio самостоятельно\. Скачать его вы можете [по ссылке](https://pvs-studio.ru/ru/pvs-studio/download/)\.

[![getTrialImageLink](https://cdn.pvs-studio.ru/media/get_trial_insert_ru.png)](https://pvs-studio.ru/ru/pvs-studio-download/)