Files
ubbook/standard_lib/stl_constructors.md
2024-05-26 23:24:57 +01:00

77 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Перегруженные конструкторы стандартной библиотеки
При проектировании стандартной библиотеки C++ было принято множество странных
решений, из-за которых приходится страдать. И исправить их не представляется
возможным из-за соображений обратной совместимости.
Одним из таких странных решений являются перегрузки конструкторов с радикально
различным поведением.
Яркий [пример](https://gcc.godbolt.org/z/e3YnYx):
```C++
using namespace std::string_literals;
std::string s1 { "Modern C++", 3 };
std::string s2 { "Modern C++"s, 3 };
std::cout << "S1: " << s1 << "\n";
std::cout << "S2: " << s2 << "\n";
```
Этот код выведет
```
S1: Mod
S2: ern C++
```
Потому что у `std::basic_string` есть один конструктор, принимающий указатель и длину строки.
А есть еще один конструктор, принимающий «что-то похожее на строку» и позицию, с которой надо из нее извлечь подстроку!
На этом причуды не заканчиваются.
```C++
std::string s1 {'H', 3};
std::string s2 {3, 'H'};
std::string s3 (3, 'H');
std::cout << "S1: " << s1.size() << "\n";
std::cout << "S2: " << s2.size() << "\n";
std::cout << "S3: " << s3.size() << "\n";
```
Этот [пример](https://gcc.godbolt.org/z/rrP67s) выведет
```
S1: 2
S2: 2
S3: 3
```
Потому что у строки есть конструктор, принимающий число `n` и символ `c`, который
нужно повторить `n` раз. А еще есть конструктор, принимающий список инициализации (`std::initializer_list<T>`), состоящий из символов. И существование этого конструктора взаимодействует с неявным приведением типов!
- `std::string s1 {'H', 3};` — строка "H\3"
- `std::string s2 {3, 'H'};` — строка "\3H"
- `std::string s3 (3, 'H');` — строка "HHH"
Аналогичной проблемой страдает `std::vector`
```C++
std::vector<int> v1 {3, 2}; // v1 == {3, 2}
std::vector<int> v2 (3, 2); // v2 == {2,2,2}
```
А еще у контейнеров есть конструктор, принимающий пару итераторов. И, казалось бы, с ними уж проблем-то не будет, но у нас есть указатели, которые также являются итераторами. А еще есть тип `bool`:
```C++
bool array[5] = {true, false, true, false, true};
std::vector<bool> vector {array, array + 5};
std::cout << vector.size() << "\n";
```
[Будет выведено](https://gcc.godbolt.org/z/jobeh6) 2, а не 5. Потому что указатели неявно приводятся к `bool`!
Собственно, эти прекрасные примеры показывают, почему «универсальная» инициализация не универсальная.
Чтобы не множить хаос в своих проектах, нужно быть осторожнее с объявлениями перегруженных конструкторов для своих типов. Лучше ввести статическую функцию, чем создавать перегруженные конструкторы, неожиданно взаимодействующие с неявным приведением типов и списками инициализации.
## Полезные ссылки
1. https://habr.com/ru/post/330402/