mirror of
https://github.com/Nekrolm/ubbook.git
synced 2025-12-18 05:14:34 +03:00
77 lines
3.9 KiB
Markdown
77 lines
3.9 KiB
Markdown
# Перегруженные конструкторы стандартной библиотеки
|
||
|
||
При проектировании стандартной библиотеки 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/ |