V1061. Extending 'std' or 'posix' namespace may result in undefined behavior.
Анализатор обнаружил расширение пространства имён 'std' или 'posix'. Несмотря на то, что такая программа успешно компилируется и исполняется, модификация данных пространств имён может привести к неопределённому поведению программы, если иное не указано стандартом.
Содержимое пространства имен 'std' определяется исключительно комитетом стандартизации, и стандарт запрещает добавлять в него:
- декларации переменных;
- декларации функций;
- декларации классов/структур/объединений;
- декларации перечислений;
- декларации шаблонов функций, классов и переменных (C++14);
Стандарт разрешает добавлять следующие специализации шаблонов, определенных в пространстве имен 'std', если они зависят хотя бы от одного определенного в программе типа (program-defined type):
- полная или частичная специализация шаблона класса;
- полная специализация шаблона функции (до C++20);
- полная или частичная специализация шаблона переменной, не лежащих в заголовочном файле '<type_traits>' (до C++20);
Однако, специализации шаблонов, лежащих внутри классов или шаблонов классов, запрещены.
Наиболее частым вариантом, когда пользователь расширяет пространство имен 'std', является добавление своей перегрузки функции 'std::swap' и полной/частичной специализации шаблона класса 'std::hash'.
Рассмотрим фрагмент кода с добавлением перегрузки 'std::swap':
template <typename T>
class MyTemplateClass
{
....
};
class MyClass
{
....
};
namespace std
{
template <typename T>
void swap(MyTemplateClass<T> &a, MyTemplateClass<T> &b) noexcept // UB
{
....
}
template <>
void swap(MyClass &a, MyClass &b) noexcept // UB since C++20
{
....
};
}
Первый шаблон функции не является специализацией 'std::swap', и такая декларация ведет к неопределенному поведению. Второй шаблон функции является специализацией, и до C++20 поведение программы определено. Однако, в данном случае можно поступить иначе: можно вынести обе функции из пространства имен 'std' и поместить их в то пространство имен, где определены классы:
template <typename T>
class MyTemplateClass
{
....
};
class MyClass
{
....
};
template <typename T>
void swap(MyTemplateClass<T> &a, MyTemplateClass<T> &b) noexcept
{
....
}
void swap(MyClass &a, MyClass &b) noexcept
{
....
};
Теперь, когда необходимо написать шаблон функции, который применяет функцию swap для двух объектов типа T, можно написать следующий код:
template <typename T>
void MyFunction(T& obj1, T& obj2)
{
using std::swap; // make std::swap visible for overload resolution
....
swap(obj1, obj2); // best match of 'swap' for objects of type T
....
}
Теперь, компилятор выберет нужную перегрузку функции на основе поиска с учетом аргументов (argument-dependent lookup, ADL) – пользовательские функции 'swap' для класса 'MyClass' и для шаблона класса 'MyTemplateClass' и стандартную версию 'std::swap' для остальных типов.
Разберем следующий пример со специализацией шаблона класса 'std::hash':
namespace Foo
{
class Bar
{
....
};
}
namespace std
{
template <>
struct hash<Foo::Bar>
{
size_t operator()(const Foo::Bar &) const noexcept;
};
}
С точки зрения стандарта этот код является валидным, и анализатор в этой ситуации не выдает предупреждение. Однако, начиная с C++11, можно и в этом случае поступить иначе, написав специализацию шаблона класса за пределами пространства имен 'std':
template <>
struct std::hash<Foo::Bar>
{
size_t operator()(const Foo::Bar &) const noexcept;
};
В отличие от пространства имен 'std', стандарт C++ запрещает абсолютно любую модификацию пространства имён 'posix':
namespace posix
{
int x; // UB
}
Дополнительная информация:
- Стандарт C++17 (working draft N4659), пункт 20.5.4.2.1
- Стандарт C++20 (working draft N4860), пункт 16.5.4.2.1
Данная диагностика классифицируется как:
|
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V1061. |