├── .editorconfig ├── LICENSE └── README.MD /.editorconfig: -------------------------------------------------------------------------------- 1 | # Documentation config 2 | root = true 3 | 4 | [*.{md,MD}] 5 | trim_trailing_whitespace = true 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dmitry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Помощник в освоении ООП 2 | 3 | Данная мини-книжка создана помочь в освоении Объектно-ориентированного программирования (ООП). 4 | 5 | **Содержание:** 6 | - [Словарь терминов](#словарь-терминов) 7 | - [Парадигмы программирования](#парадигмы-программирования) 8 | - [Императивные парадигмы](#императивные-парадигмы) 9 | - [Процедурное программирование](#процедурное-программирование) 10 | - [Объектно-ориентированное программирование](#объектно-ориентированное-программирование) 11 | - [Декларативные парадигмы](#декларативные-парадигмы) 12 | - [Функциональное программирование](#функциональное-программирование) 13 | - [Что такое ООП? Зачем оно нужно? Как оно используется?](#что-такое-ооп-зачем-оно-нужно-как-оно-используется) 14 | - [Как проектировать программы с ООП?](#как-проектировать-программы-с-ооп) 15 | - [Паттерны проектирования](#паттерны-проектирования) 16 | - [Базовые принципы ООП](#базовые-принципы-ооп) 17 | - [Наследование](#наследование) 18 | - [Полиморфизм](#полиморфизм) 19 | - [Инкапсуляция](#инкапсуляция) 20 | - [Что такое класс?](#что-такое-класс) 21 | - [В чем различия между классом и структурой?](#в-чем-различия-между-классом-и-структурой) 22 | - [Что такое объект](#что-такое-объект) 23 | - [Отличия между классом и объектом](#отличия-между-классом-и-объектом) 24 | - [Модификаторы доступа](#модификаторы-доступа) 25 | - [Применение модификаторов к членам класса](#применение-модификаторов-к-членам-класса) 26 | - [Public](#public) 27 | - [Private](#private) 28 | - [Protected](#protected) 29 | - [Применение модификаторов к унаследованным классам](#применение-модификаторов-к-унаследованным-классам) 30 | - [Виртуальная функция](#виртуальная-функция) 31 | - [Чистая виртуальная функция](#чистая-виртуальная-функция) 32 | - [Таблица виртуальных функций](#таблица-виртуальных-функций) 33 | - [Friend functions](#friend-functions) 34 | - [Статические поля класса](#статические-поля-класса) 35 | - [Статические методы класса](#статические-методы-класса) 36 | - [Scope resolution operator](#scope-resolution-operator) 37 | - [Что такое конструктор?](#что-такое-конструктор) 38 | - [Default constructor](#default-constructor) 39 | - [Parameterized constructor](#parameterized-constructor) 40 | - [Вызов конструктора](#вызов-конструктора) 41 | - [Initializer list](#initializer-list) 42 | - [Применение Initializer list](#применение-initializer-list) 43 | - [Что такое деструктор?](#что-такое-деструктор) 44 | - [Виртуальный деструктор](#виртуальный-деструктор) 45 | - [Абстрактный класс](#абстрактный-класс) 46 | - [Геттеры \& Сеттеры](#геттеры--сеттеры) 47 | - [Связь с инкапсуляцией](#связь-с-инкапсуляцией) 48 | - [Разделение класса на описание и имплементацию](#разделение-класса-на-описание-и-имплементацию) 49 | - [Достоинства данного метода](#достоинства-данного-метода) 50 | - [RAII](#raii) 51 | - [Действия в случае исключения](#действия-в-случае-исключения) 52 | - [Smart pointers](#smart-pointers) 53 | - [Отличия между unique\_ptr и shared\_ptr](#отличия-между-unique_ptr-и-shared_ptr) 54 | 55 | --- 56 | 57 | ## Словарь терминов 58 | 59 | - **Полиморфизм** - это способность языка программирования вызывать необходимую имплементацию в зависимости от типа данных. В данном справочнике будет рассмотрен *Runtime* полиморфизм. 60 | - **Метод** - методом называется какая-либо функция класса 61 | - **Поле класса** - полем называется какая-либо переменная класса 62 | 63 | --- 64 | 65 | ## Парадигмы программирования 66 | 67 | Прежде чем приступать к изучению ООП и ответу на вопрос зачем оно нужно, сначала надо изучить самые основные парадигмы и понять разницу между ними. 68 | 69 | **Парадигма** - это стиль написания программ, подходы к написанию кода. 70 | 71 | Парадигмы делятся на 2 типа: **[императивная](https://ru.wikipedia.org/wiki/%D0%98%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)** и **[декларативная](https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BB%D0%B0%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)**. Императивная парадигма программированию задает последовательность команд, *КАК* надо что-то сделать, а декларативное - *ЧТО* надо сделать. 72 | 73 | ### Императивные парадигмы 74 | 75 | #### Процедурное программирование 76 | 77 | Вся программа - набор переменных, их состояний, они постоянно меняются пока программа идет к конечному результату. Программа содержит набор подпрограмм (процедур), с помощью которых вычисляются нужные значения, меняются какие-то переменные. В языках C и C++ их роль выполняют функции. 78 | 79 | #### Объектно-ориентированное программирование 80 | 81 | Именно эту парадигму мы рассматриваем в данной книжке. 82 | 83 | --- 84 | 85 | ### Декларативные парадигмы 86 | 87 | #### Функциональное программирование 88 | 89 | Абсолютно противоположная императивному парадигма. Тут нет переменных, нет состояний. 90 | Новые переменные создаются путем передачи нужных аргументов в функции, где уже создаются новые значения, а из тех же функций снова в другие. 91 | Поэтому основной принцип работы программы - рекурсия. 92 | Это защищает данные от изменения, так как на каждом шаге программы мы работает всегда с новыми данными, и повлиять на 93 | прошлые никак не можем. То есть программа работает только с локальными данными, передаваемыми в функции. 94 | 95 | --- 96 | 97 | ## Что такое ООП? Зачем оно нужно? Как оно используется? 98 | 99 | ООП построена на основе процедурного программирования. 100 | 101 | Однако оно решает её самую главную проблему - в процедурном значения переменных могут меняться извне, 102 | потому что они доступны глобально. Вызывая какие-нибудь функции, мы рискуем повлиять на работу совершенно других 103 | переменных/функций. ООП же, в свою очередь, завязан на работе только с отдельными объектами. 104 | Это защищает данные от внезапного изменения, так как вызывая какой-нибудь метод, пользователь работает лишь с конкретным объектом. 105 | 106 | --- 107 | 108 | ## Как проектировать программы с ООП? 109 | 110 | 1. Как и приступая к написанию кода большинства программ, для начала следует разобраться с архитектурой программы, продумать все алгоритмы, а не сразу начинать писать код, требуется полностью понять как должна работать будущая программа. 111 | 2. Стоит помнить о такой вещи в ООП, как принцип единственной ответственности. Каждый класс должен быть ответственен только за одну вещь и отвечать за конкретную цель. 112 | 113 | При изменении кода в одном месте, это может затронуть совершенно другие функции, изменение которых пользователь может совершенно не ожидать, так как они преследуют совершенно другие цели, но из-за того, что все имплементировано в одном классе, мы рискуем получить ошибку. При чем, принцип единственной ответственности предполагает, что вся важнейшая имплементация будет инкапсулирована. 114 | 3. Программа должна быть разбита на отдельные модули, каждый модуль отвечает за отдельные вещи. 115 | 116 | При чем, они должны быть связаны минимально, то есть изменение кода в одном классе должно быть относительно безболезненным, чтобы потом не пришлось переделывать всю программу. 117 | 4. Программа должна быть читабельна, а структура программы понятна, не должно быть анархии в коде. 118 | 119 | Один из способов сделать это - при написании программы отделять интерфейс программы от их имплементации, инкапсулируя имплементацию нижнего уровня, а также ненужную информацию, которая потребуется лишь в ходе работы самой программы, то есть вещи которые происходят внутри нее, но не в ходе ее использования, предоставляя лишь нужный интерфейс к доступу функций. 120 | 121 | --- 122 | 123 | ### Паттерны проектирования 124 | 125 | **Паттерн проектирования** - это общепринятый метод решения какой-либо проблемы в программировании. Это НЕ готовая программа, а лишь описание ее решения. Паттерны упрощают проектирование программ за счет того, что использовать готовое решение гораздо проще и надежнее, ведь паттерны это уже закрепленные и проверенные практики в мире программирования. Более того, это также сводит к минимуму возможность возникновения ошибок. 126 | 127 | Примеры паттернов: 128 | 1. *Итератор* - это паттерн, который позволяет последовательно обходить все элементы объекта, не зная его внутренней имплементации. 129 | 130 | Пример: 131 | 132 | ```c++ 133 | vector data; 134 | vector ::iterator it; 135 | int value = 0; 136 | 137 | for (it = data.begin(); it != data.end(); it++) { 138 | value++; 139 | *it = value; 140 | } 141 | ``` 142 | 143 | 2. *[Абстрактная фабрика](https://refactoring.guru/ru/design-patterns/abstract-factory)* 144 | 3. И многие другие. Есть отличный сайт по паттернам проектирования - [Refactoring.Guru](https://refactoring.guru/ru/design-patterns/catalog) 145 | 146 | --- 147 | 148 | ## Базовые принципы ООП 149 | 150 | ### Наследование 151 | 152 | **Наследование** - это возможность создать новый дочерний класс на основе родительского класса, но с расширением функциональности. При чем, все методы родительского класса будут доступны и в дочернем. 153 | 154 | Пример: 155 | 156 | ```c++ 157 | #include 158 | using namespace std; 159 | class Base { 160 | public: 161 | string s; 162 | void initialize_string(const string& data) { 163 | s = data; 164 | } 165 | }; 166 | class Derived : public Base { // Дочерний класс 167 | public: 168 | void print_string() { 169 | cout << s; 170 | } 171 | }; 172 | int main() { 173 | Derived object; 174 | object.initialize_string("Derived class was created"); 175 | object.print_string(); // Derived class was created 176 | return 0; 177 | } 178 | ``` 179 | 180 | ### Полиморфизм 181 | 182 | **Полиморфизм** - это возможность использовать объекты c одинаковым интерфейсом одного класса, но на основе типа объекта. 183 | 184 | Это значит то, что основной интерфейс у них не отличается ничем друг от друга, но некоторые методы должны работать в зависимости от типа объекта. Тогда создается несколько методов, имеющих одинаковое объявление, но различную имплементацию, внутри одного класса и его потомков. Более подробно данная особенность будет рассмотрена позднее в главе "Виртуальные функции". 185 | 186 | Пример: 187 | 188 | ```c++ 189 | #include 190 | using namespace std; 191 | 192 | class Base { 193 | public: 194 | virtual void print() { 195 | cout << "Base class print function was called" << endl; 196 | } 197 | }; 198 | 199 | class Derived : public Base { 200 | public: 201 | void print() override { // Пример полиморфизма: функция была объявлена ранее в родительском классе, 202 | // однако при вызове функции print() из объекта типа Derived будет вызвана данная функция, а не 203 | // родительского класса 204 | cout << "Derived class print function was called" << endl; 205 | } 206 | }; 207 | 208 | int main () { 209 | Base* parent = new Base; 210 | Derived child; 211 | 212 | parent->print(); // Base class print function was called 213 | 214 | delete parent; 215 | 216 | parent = &child; // Смена типа объекта на тип дочернего класса 217 | 218 | parent->print(); // Derived class print function was called 219 | return 0; 220 | } 221 | ``` 222 | 223 | ### Инкапсуляция 224 | 225 | **Инкапсуляция** - это сокрытие деталей имплементации в классе. Доступ к необходимым данным предоставлен через специальные паблик методы. 226 | Следование данному принципу защищает данные от внезапного изменения извне и от непредвиденных ошибок. 227 | 228 | Пример: 229 | 230 | ```c++ 231 | #include 232 | using namespace std; 233 | 234 | class Rectangle { 235 | protected: 236 | double width, height; 237 | public: 238 | void set_values(int a, int b) { // Скрытые переменные должны инициализироваться паблик методами, 239 | // таким образом защищая их от изменения извне 240 | width = a; 241 | height = b; 242 | } 243 | }; 244 | 245 | int main () { 246 | Rectangle figure = Rectangle(); 247 | figure.set_values(4, 10); 248 | return 0; 249 | } 250 | ``` 251 | 252 | --- 253 | 254 | ## Что такое класс? 255 | 256 | **Класс** - это тип данных, задаваемый пользователем, следовательно, функциональность и имплементация класса полностью зависят от самого пользователя. 257 | 258 | Создание класса: 259 | 260 | ```c++ 261 | #include 262 | using namespace std; 263 | 264 | class Foo { 265 | public: 266 | Foo() { 267 | cout << "Class was created"; 268 | }; 269 | }; 270 | 271 | int main() { 272 | return 0; 273 | } 274 | ``` 275 | 276 | --- 277 | 278 | ### В чем различия между классом и структурой? 279 | 280 | Класс от структуры отличает то, что в классе все поля по умолчанию имеют модификатор private, а в структуре - public. 281 | 282 | --- 283 | 284 | ## Что такое объект 285 | 286 | **Объект** - это конкретный экземпляр класса, функциональность которого полностью ограничена самим классом. Класс - всего лишь описание, на него не 287 | выделяется памяти. Для того, чтобы стало возможным записывать данные и вызывать методы, требуется создать объект. Все состояния переменных класса 288 | будут принадлежать конкретному объекту (кроме статических полей, которые будут рассмотрены позднее). 289 | 290 | Создание объекта: 291 | 292 | ```c++ 293 | #include 294 | using namespace std; 295 | 296 | class Foo { 297 | public: 298 | Foo() { 299 | cout << "Class was created"; 300 | } 301 | }; 302 | 303 | int main() { 304 | Foo object; // Объект создан 305 | return 0; 306 | } 307 | ``` 308 | 309 | --- 310 | 311 | ### Отличия между классом и объектом 312 | 313 | Класс лишь определяет доступные поведения, методы, возможные состояния экземпляров класса (объектов), а сами объекты - 314 | это конкретные экземпляры класса, в них мы можем вызывать методы, создавать переменные, при чем все они могут иметь 315 | различные состояния, но будут контролироваться классом. Все доступные методы и поведение описано в классе, через объекты 316 | происходит взаимодействие с классом и хранение данных. 317 | 318 | --- 319 | 320 | ## Модификаторы доступа 321 | 322 | Модификаторы доступа позволяют настраивать область видимости в программе конкретных методов/переменных, описанных в классе. 323 | 324 | ### Применение модификаторов к членам класса 325 | 326 | #### Public 327 | 328 | **Public** - данный модификатор позволяет получать доступ к переменным/методам из любого места программы. Никакие данные не скрыты, 329 | область видимости - глобальная. 330 | 331 | Пример: 332 | 333 | ```c++ 334 | #include 335 | using namespace std; 336 | 337 | class Foo { 338 | public: 339 | string s; 340 | }; 341 | 342 | int main() { 343 | Foo object; 344 | object.s = "Class was created"; // обращение к переменной класса без использования паблик-методов 345 | return 0; 346 | } 347 | ``` 348 | 349 | #### Private 350 | 351 | **Private** - данный модификатор ограничивает область видимости переменных/методов, делая их доступными только в самом классе. 352 | Доступ к ним можно будет получить только через паблик методы класса. 353 | 354 | Пример: 355 | 356 | ```c++ 357 | #include 358 | using namespace std; 359 | 360 | class Foo { 361 | private: 362 | string s; 363 | public: 364 | void initialize_string(const string& data) { 365 | s = data; 366 | } 367 | 368 | void print_string() { 369 | cout << s; 370 | } 371 | }; 372 | 373 | int main() { 374 | Foo object; 375 | object.initialize_string("private modifier is set"); // Для того, чтобы получить доступ к переменной, 376 | // объявленной как private, требуется обращаться к ней через паблик функции класса 377 | object.print_string(); // private modifier is set 378 | return 0; 379 | } 380 | ``` 381 | 382 | #### Protected 383 | 384 | **Protected** - данный модификатор отличается от private только тем, что данные, объявленные как protected, могут быть получены из дочерних классов, созданных на основе родительского (также это известно как наследование, разобранное и описанное выше). 385 | 386 | Пример: 387 | 388 | ```c++ 389 | #include 390 | using namespace std; 391 | 392 | class Base { 393 | //private: // Ошибка: получить доступ к переменной в дочернем классе будет невозможно 394 | protected: 395 | string s; 396 | }; 397 | 398 | class Derived : Base { 399 | public: 400 | void initialize_string(const string& data) { 401 | s = data; 402 | } 403 | 404 | void print_string() { 405 | cout << s; 406 | } 407 | }; 408 | 409 | int main() { 410 | Derived object; 411 | object.initialize_string("protected modifier is set"); 412 | object.print_string(); // protected modifier is set 413 | return 0; 414 | } 415 | ``` 416 | 417 | --- 418 | 419 | ### Применение модификаторов к унаследованным классам 420 | 421 | Все описанные модификаторы доступа применимы также и к модификаторам доступа наследственных классов: 422 | 423 | 1. **Public** - при применении к унаследованному классу означает то, что доступ к Base классу смогут получить **все классы, которые имеют доступ к дочернему-классу Derived** 424 | 425 | ```c++ 426 | Class Derived : public Base {}; 427 | ``` 428 | 429 | 2. **Protected** - при применении к унаследованному классу означает то, что доступ к Base классу смогут получить все те классы, которые **были унаследованы от дочернего класса Derived** 430 | 431 | ```c++ 432 | Class Derived : protected Base {}; 433 | ``` 434 | 435 | 3. **Private** - при применении к унаследованному классу означает то, что доступ к Base классу сможет получить **только дочерний класс** 436 | 437 | ```c++ 438 | Class Derived : private Base {}; 439 | ``` 440 | 441 | --- 442 | 443 | ## Виртуальная функция 444 | 445 | **Виртуальная функция** (virtual function) - это метод в родительском классе, который можно переопределить в его дочерних классах. Это позволяет пользователю вызывать необходимые методы в зависимости от типа объекта (см. [#Базовые принципы ООП: Полиморфизм](#полиморфизм)). 446 | 447 | Пример: 448 | 449 | ```c++ 450 | #include 451 | using namespace std; 452 | 453 | class Base { 454 | public: 455 | void print() { 456 | cout << "In parent class"; 457 | } 458 | }; 459 | 460 | class Derived : public Base { 461 | public: 462 | void print() { 463 | cout << "In child class"; 464 | } 465 | }; 466 | 467 | int main () { 468 | Base* baseptr = new Base; // Объект типа родительского класса 469 | 470 | baseptr->print(); // In parent class 471 | cout << endl; 472 | 473 | delete baseptr; 474 | baseptr = new Derived; // Объект типа дочернего класса 475 | 476 | baseptr->print(); // In parent class 477 | delete baseptr; 478 | return 0; 479 | } 480 | ``` 481 | 482 | Как видно из примера, функция `print()`, вызванная из объекта типа `Derived`, в любом случае вызывает функцию родительского класса. 483 | Это происходит потому, что функция `print()` вызывается в зависимости от типа *указателя*, а не объекта. Виртуальные функции же решают эту проблему и позволяют вызывать методы на основе типа *объекта*, что известно как **Runtime polymorphism**. 484 | 485 | Объявляем функцию `print()` как *виртуальную*: 486 | 487 | ```c++ 488 | #include 489 | using namespace std; 490 | 491 | class Base { 492 | public: 493 | virtual void print() { 494 | cout << "In parent class"; 495 | } 496 | }; 497 | 498 | class Derived : public Base { 499 | public: 500 | void print() override { 501 | cout << "In child class"; 502 | } 503 | }; 504 | 505 | int main () { 506 | Base* baseptr = new Base; // Объект типа родительского класса 507 | 508 | baseptr->print(); // In parent class 509 | cout << endl; 510 | 511 | delete baseptr; 512 | baseptr = new Derived; // Объект типа дочернего класса 513 | 514 | baseptr->print(); // In child class 515 | delete baseptr; 516 | return 0; 517 | } 518 | ``` 519 | 520 | --- 521 | 522 | ### Чистая виртуальная функция 523 | 524 | **Чистая виртуальная функция** (pure virtual function) - это виртуальная функция, не имеющая имплементации в родительском классе. Необходимость в ней возникает, когда пользователь не знает, какая имплементация должна быть у виртуальной функции в родительском классе, так как ее имплементация зависит от типа объекта. Чистую виртуальную функцию требуется инициализировать нулевым значением. 525 | 526 | Пример: 527 | 528 | ```c++ 529 | #include 530 | #include 531 | using namespace std; 532 | 533 | class Shape { 534 | protected: 535 | int width = 0, height = 0; 536 | public: 537 | void set_values(int a, int b) { 538 | width = a; 539 | height = b; 540 | } 541 | 542 | // Тип объекта не известен (квадрат или прямоугольник), 543 | // следовательно имплементация метода в родительском классе невозможна 544 | virtual double get_diagonal() = 0; 545 | 546 | }; 547 | 548 | class Square : public Shape { 549 | public: 550 | double get_diagonal() override { 551 | return sqrt(2 * pow(width, 2)); 552 | } 553 | }; 554 | 555 | class Rectangle : public Shape { 556 | public: 557 | double get_diagonal() override { 558 | return sqrt(pow(width, 2) + pow(height, 2)); 559 | } 560 | }; 561 | 562 | int main () { 563 | Shape* shape; 564 | Square square; 565 | Rectangle rectangle; 566 | 567 | shape = □ 568 | shape->set_values(5, 5); 569 | cout << shape->get_diagonal() << endl; // 7.07107 570 | 571 | shape = &rectangle; 572 | shape->set_values(4, 3); 573 | cout << shape->get_diagonal(); // 5 574 | return 0; 575 | } 576 | ``` 577 | 578 | --- 579 | 580 | ## Таблица виртуальных функций 581 | 582 | Как было уже сказано ранее, виртуальные функции поддерживают **Runtime** полиморфизм. Он имплементируется автоматически компилятором с помощью 583 | виртуальной таблицы функций. Говоря кратко, виртуальная таблица класса - это список указателей на методы, которые должны будут вызываться, 584 | в зависимости от типа объекта. Виртуальная таблица создается для каждого класса, который имеет виртуальный метод, или для унаследованных от него. 585 | Если виртуальная функция была переопределена в классе, то виртуальная таблица для данного класса будет иметь указатель на метод в данном классе, 586 | иначе указатель будет иметь адрес метода в классе, в котором виртуальный метод был впервые объявлен (в основном, родительский класс). 587 | 588 | --- 589 | 590 | ## Friend functions 591 | 592 | **Friend function** - это функция, объявленная вне области класса и его потомков, однако имеющая доступ к скрытым данным (private и protected) в нем. 593 | 594 | Пример: 595 | 596 | ```c++ 597 | #include 598 | using namespace std; 599 | 600 | class Foo { 601 | private: 602 | int a; 603 | public: 604 | friend void print_value(Foo Foo); // Объявление дружественной функции (keyword "friend") 605 | 606 | int set_values(int value) { 607 | a = value; 608 | } 609 | }; 610 | 611 | void print_value(Foo Foo) { // Дружественная функция, имеющая доступ к скрытым данным в классе Foo 612 | cout << Foo.a; 613 | } 614 | 615 | int main () { 616 | Foo foo = Foo(); 617 | foo.set_values(5); 618 | print_value(foo); // 5 619 | return 0; 620 | } 621 | ``` 622 | 623 | --- 624 | 625 | ## Статические поля класса 626 | 627 | **Статическое поле** - это член класса, который имеют общее состояние во всех созданных объектах. Следовательно, при изменении значения члена 628 | класса в одном объекте, оно изменится и во всех созданных объектах того же типа класса. 629 | 630 | Пример: 631 | 632 | ```c++ 633 | #include 634 | using namespace std; 635 | 636 | class Foo { 637 | public: 638 | static int s; 639 | }; 640 | 641 | int Foo::s = 0; 642 | 643 | int main() { 644 | Foo first; 645 | Foo second; 646 | 647 | first.s = 5; 648 | 649 | cout << first.s; // 5 650 | cout << endl; 651 | cout << second.s; // 5 652 | // Переменная s во втором объекте имеет то же состояние, что и первый объект 653 | return 0; 654 | } 655 | ``` 656 | 657 | Вопрос: Для чего требуется выносить инициализацию переменной за класс? 658 | 659 | ```c++ 660 | int Foo::s = 0; 661 | ``` 662 | 663 | Ответ: Так как статическая переменная не является частью конкретных объектов, а принадлежит самому классу, то и инициализируется она при начале работы 664 | программы. Также, если класс со статическим полем определен в хедере, то при включении хедера во множественных местах, переменная будет объявляться 665 | более одного раза, что запрещено. Поэтому ее определение требуется вынести за область видимости класса. 666 | 667 | Важно отметить, что пользователь может получить к ним доступ, даже не создавая объект. Такой метод доступа 668 | к данным наиболее предпочтителен, чем доступ через объекты, так как корректнее считать, что статическое поле относится к самому *классу*, а не к 669 | конкретному объекту. Пример: 670 | 671 | ```c++ 672 | #include 673 | using namespace std; 674 | 675 | class Foo { 676 | public: 677 | static int s; 678 | }; 679 | 680 | int Foo::s = 0; 681 | 682 | int main() { 683 | Foo first; 684 | Foo second; 685 | 686 | first.s = 5; 687 | 688 | cout << first.s; // 5 689 | cout << endl; 690 | cout << second.s; // 5 691 | // Переменная s во втором объекте имеет то же состояние, что и первый объект 692 | return 0; 693 | } 694 | ``` 695 | 696 | Также следует отметить, что при инициализации доступ к статической переменной может быть получен независимо от ее модификаторов доступа. 697 | 698 | И наконец логичным заключением из вышенаписанного является то, что что статическая переменная хранит свое состояние от начала работы программы и до конца, ведь она принадлежит классу, а не объекту. 699 | 700 | --- 701 | 702 | ## Статические методы класса 703 | 704 | **Статический метод** - статический метод так же, как и статическая переменная, может быть вызван без создания объекта. Также он нужен для того, 705 | чтобы получить доступ к скрытым статическим полям в классе, так как получить доступ к скрытой переменной напрямую невозможно (но ее все также можно 706 | инициализировать). Для того, чтобы это стало возможным, требуется создать статический класс. 707 | 708 | Пример: 709 | 710 | ```c++ 711 | #include 712 | using namespace std; 713 | 714 | class Foo { 715 | private: 716 | static int s; 717 | public: 718 | static int get_value() { 719 | return s; 720 | } 721 | }; 722 | 723 | int Foo::s = 10; 724 | 725 | int main() { 726 | cout << Foo::get_value(); // 10 727 | return 0; 728 | } 729 | ``` 730 | 731 | Следует отметить, что статический метод может получить доступ только к таким же *static* полям класса, но не *non-static* переменным. 732 | 733 | Также определение метода может быть вынесено за область видимости класса. Пример: 734 | 735 | ```c++ 736 | #include 737 | using namespace std; 738 | 739 | class Foo { 740 | private: 741 | static int s; 742 | public: 743 | static int get_value(); 744 | }; 745 | 746 | int Foo::get_value() { 747 | return s; 748 | } 749 | 750 | int Foo::s = 10; 751 | 752 | int main() { 753 | cout << Foo::get_value(); // 10 754 | return 0; 755 | } 756 | ``` 757 | 758 | --- 759 | 760 | ## Scope resolution operator 761 | 762 | [Scope resolution operator](https://en.wikipedia.org/wiki/Scope_resolution_operator) `::` позволяет получать доступ к области видимости какого-либо класса, и, соответственно, к переменным/методам в нем. 763 | 764 | --- 765 | 766 | ## Что такое конструктор? 767 | 768 | **Конструктор** - это специальный метод класса, который инициализирует объекты. Конструктор вызывается автоматически при создании объекта. 769 | Если пользователь не написал свой конструктор, компилятор автоматически генерирует пустой конструктор. 770 | 771 | Существует 2 типа конструкторов: 772 | 773 | ### Default constructor 774 | 775 | **Стандартный конструктор** - это конструктор, который не имеет никаких параметров. 776 | 777 | Пример: 778 | 779 | ```c++ 780 | #include 781 | using namespace std; 782 | 783 | class Foo { 784 | public: 785 | int a, b; 786 | 787 | Foo() { // Конструктор 788 | a = 5; 789 | b = 7; 790 | } 791 | }; 792 | 793 | int main () { 794 | Foo construct; 795 | 796 | cout << construct.a; // 5 797 | cout << endl; 798 | cout << construct.b; // 7 799 | return 0; 800 | } 801 | ``` 802 | 803 | **Стандартный конструктор будет автоматически генерироваться компилятором, если не были объявлены никакие другие специализированные конструкторы.** 804 | В противном случае необходимо имплементировать стандартный конструктор самому пользователю. Без стандартного конструктора невозможно создание 805 | дочерних классов или объектов данного класса в дочерних классах. 806 | 807 | ### Parameterized constructor 808 | 809 | **Parameterized constructor**, в отличие от стандартного конструктора, имеет параметры в своем объявлении и позволяет инициализировать поля класса 810 | переданными параметрами. 811 | 812 | Пример: 813 | 814 | ```c++ 815 | #include 816 | using namespace std; 817 | 818 | class Foo { 819 | public: 820 | int a, b; 821 | 822 | Foo(int a1, int a2) { // Конструктора 823 | a = a1; 824 | b = a2; 825 | } 826 | }; 827 | 828 | int main () { 829 | Foo construct(10, 15); 830 | 831 | cout << construct.a; // 10 832 | cout << endl; 833 | cout << construct.b; // 15 834 | return 0; 835 | } 836 | ``` 837 | 838 | ### Вызов конструктора 839 | 840 | Вызвать конструктор можно двумя способами: 841 | 842 | 1. Для *стандартного* конструктора: 843 | 844 | ```c++ 845 | Construct explicit_call = Construct(); // Явный вызов 846 | Construct implicit_call; // Неявный вызов 847 | ``` 848 | 849 | 2. Для *parameterized* конструктора: 850 | 851 | ```c++ 852 | Construct explicit_call = Construct(parameters); // Явный вызов 853 | Construct implicit_call(parameters); // Неявный вызов 854 | ``` 855 | 856 | Существует значимое отличие явного вызова от неявного при использовании стандартного конструктора. Если конструктор не имплементирован, и 857 | происходит явный вызов, то все значения будут инициализированы нулями. Однако, при использовании неявного вызова компилятор будет использовать 858 | стандартный конструктор, который ***не инициализирует*** значения нулями, присваивая случайные значения. 859 | 860 | --- 861 | 862 | ## Initializer list 863 | 864 | **Initializer list** требуется для того, чтобы инициализировать переменные класса. 865 | 866 | Однако существует огромная разница между инициализацией членов класса с помощью initializer list и присваиванием значений в теле конструктора. Initializer list принимает аргументы и *сразу же инициализирует* необходимые данные в отличие от присваивания в теле конструктора, где сначала компилятором *создается копия* переданных данных и только после этого происходит присваивание. Следовательно, использование первого метода положительно влияет на скорость выполнения программ. 867 | 868 | Пример использования: 869 | 870 | ```c++ 871 | #include 872 | using namespace std; 873 | 874 | class Foo { 875 | private: 876 | int a, b; 877 | public: 878 | Foo(int i, int j) : a(i), b(j) {}; // Initializer list 879 | 880 | int get_a() { 881 | return a; 882 | } 883 | 884 | int get_b() { 885 | return b; 886 | } 887 | }; 888 | 889 | int main() { 890 | Foo foo = Foo(5, 8); 891 | 892 | cout << foo.get_a(); // 5 893 | cout << endl; 894 | cout << foo.get_b(); // 8 895 | return 0; 896 | } 897 | ``` 898 | 899 | ### Применение Initializer list 900 | 901 | Существует несколько случаев, где применим только *initializer list*: 902 | 903 | - Для инициализации константных переменных в классе: 904 | 905 | ```c++ 906 | #include 907 | using namespace std; 908 | 909 | class Foo { 910 | private: 911 | const int s; 912 | public: 913 | // Valid 914 | Foo(int i = 0) : s(i) {}; 915 | 916 | /* Invalid 917 | Foo(int i = 0) { 918 | s = i; 919 | }; 920 | */ 921 | 922 | int get_value() { 923 | return s; 924 | }; 925 | }; 926 | 927 | int main() { 928 | Foo foo = Foo(19); 929 | 930 | cout << foo.get_value(); // 19 931 | return 0; 932 | } 933 | ``` 934 | 935 | - Для инициализации Reference Members (переменных, которым присвоен адрес какой-либо другой переменной) 936 | 937 | ```c++ 938 | #include 939 | using namespace std; 940 | 941 | class Foo { 942 | private: 943 | int& s; // Переменная содержит адрес переменной x и изменяется при изменении последней 944 | public: 945 | // Valid 946 | Foo(int& i) : s(i) {}; 947 | 948 | /* Invalid: переменной s невозможно присвоить адрес переменной i таким образом 949 | Foo (int& i) { 950 | s = i; 951 | } 952 | */ 953 | 954 | int get_value() { 955 | return s; 956 | }; 957 | }; 958 | 959 | int main() { 960 | int x = 10; 961 | 962 | Foo foo = Foo(x); 963 | 964 | cout << foo.get_value(); // 10 965 | cout << endl; 966 | 967 | x = 25; 968 | 969 | cout << foo.get_value(); // 25 970 | return 0; 971 | } 972 | ``` 973 | 974 | - Для инициализации объектов, которые не имеют стандартного конструктора. 975 | 976 | Пример: 977 | 978 | ```c++ 979 | #include 980 | using namespace std; 981 | 982 | class Foo { 983 | protected: 984 | int i; 985 | public: 986 | Foo(int a) { 987 | i = a; 988 | cout << "Foo's constructor called" << endl; 989 | }; 990 | }; 991 | 992 | class Bar { 993 | private: 994 | Foo foo; 995 | public: 996 | // Valid: инициализация объекта foo с использованием parameterized конструктора 997 | Bar(int a) : foo(a) { 998 | cout << "Bar's constructor called"; 999 | }; 1000 | 1001 | /* Invalid: невозможно инициализировать объект таким образом 1002 | Bar(int a) { 1003 | a_object = Foo(a); 1004 | } 1005 | */ 1006 | }; 1007 | 1008 | int main() { 1009 | Bar object(10); 1010 | return 0; 1011 | } 1012 | ``` 1013 | 1014 | - Для инициализации членов класса, которые имеют такое же имя, как и переданный параметр 1015 | 1016 | Пример: 1017 | 1018 | ```c++ 1019 | #include 1020 | using namespace std; 1021 | 1022 | class Foo { 1023 | private: 1024 | int i; 1025 | public: 1026 | // Valid 1027 | Foo(int i) : i(i) { 1028 | cout << "Data member i was initialized to: " << Foo::i; 1029 | }; 1030 | 1031 | /* Invalid: переменная i класса Foo инициализируется не должным образом, выводя случайные значения из мусора 1032 | Foo(int i) { 1033 | i = i; 1034 | cout << "Data member i was initialized to: " << Foo::i; // Data member i was initialized to: 432910816 1035 | } 1036 | */ 1037 | }; 1038 | 1039 | int main() { 1040 | Foo foo(10); // Data member i was initialized to: 10 1041 | return 0; 1042 | } 1043 | ``` 1044 | 1045 | --- 1046 | 1047 | ## Что такое деструктор? 1048 | 1049 | **Деструктор** - это специальный метод класса, который высвобождает память, которая использовалась во время жизненного цикла объекта, и уничтожает 1050 | объект. Он вызывается автоматически при завершении жизненного цикла объекта. В деструкторе можно описать пользовательские действия, и перед тем, 1051 | как объект полностью уничтожится, требуемые действия будут произведены. Например: закрыть открытый файл, высвободить динамически выделенную 1052 | память и т.д. 1053 | 1054 | Важно отметить, что деструктор вызывается только если объект был полностью инициализирован без ошибок и без исключений. 1055 | 1056 | Пример простого деструктора: 1057 | 1058 | ```c++ 1059 | #include 1060 | using namespace std; 1061 | 1062 | class Destruct { 1063 | public: 1064 | int a = 5, b = 10; 1065 | 1066 | ~Destruct() { 1067 | cout << "Object was deleted"; 1068 | } 1069 | }; 1070 | 1071 | int main () { 1072 | Destruct example; 1073 | return 0; // Object was deleted 1074 | } 1075 | ``` 1076 | 1077 | ### Виртуальный деструктор 1078 | 1079 | Виртуальный деструктор требуется для полного корректного уничтожения объекта, где содержится хотя бы 1 виртуальный метод. Уничтожение объекта по указателю типа *родительского класса* является неопределенным поведением, так как происходит вызов деструктора по типу *указателя*, а не типу объекта (это описано подробнее в главе о [Виртуальных функциях](#виртуальная-функция)). Для того, чтобы полностью удалить объект, требуется добавить виртуальные деструкторы во все унаследованные классы. 1080 | 1081 | Пример: 1082 | 1083 | ```c++ 1084 | #include 1085 | using namespace std; 1086 | 1087 | class Base { 1088 | public: 1089 | Base() { 1090 | cout << "Constructing Base" << endl; 1091 | } 1092 | 1093 | virtual ~Base() { 1094 | cout << "Destructing Base" << endl; 1095 | } 1096 | }; 1097 | 1098 | class Derived : public Base { 1099 | public: 1100 | Derived() { 1101 | cout << "Constructing Derived" << endl; 1102 | } 1103 | 1104 | ~Derived() override { 1105 | cout << "Destructing Derived" << endl; 1106 | } 1107 | }; 1108 | 1109 | int main() { 1110 | Base *d = new Derived(); 1111 | // Constructing Base 1112 | // Constructing Derived 1113 | 1114 | delete d; 1115 | // Destructing Derived 1116 | // Destructing Base 1117 | return 0; 1118 | } 1119 | ``` 1120 | 1121 | --- 1122 | 1123 | ## Абстрактный класс 1124 | 1125 | **Абстрактный класс** - это класс, который имеет чистую виртуальную функцию (см. [#Чистая виртуальная функция](#чистая-виртуальная-функция)). 1126 | Абстрактный класс не может быть инициализирован. Он используется для дальнейшего создания объектов конкретного типа, которые будут иметь 1127 | функциональность родительского класса, но различную имплементацию виртуальных и чистых виртуальных методов. 1128 | - Если виртуальная функция не была переопределена в дочернем классе, дочерний класс также будет являться абстрактным классом. 1129 | 1130 | Пример: 1131 | 1132 | ```c++ 1133 | #include 1134 | using namespace std; 1135 | 1136 | class Foo { 1137 | public: 1138 | virtual void pure_virtual_function() = 0; 1139 | }; 1140 | 1141 | int main() { 1142 | // Foo foo; // Ошибка создания объекта типа абстрактного класса 1143 | return 0; 1144 | } 1145 | ``` 1146 | 1147 | --- 1148 | 1149 | ## Геттеры & Сеттеры 1150 | 1151 | **Getter** - метод класса, возвращающий данные пользователю 1152 | 1153 | **Setter** - метод класса, инициализирующий данные переданными аргументами. 1154 | 1155 | Пример: 1156 | 1157 | ```c++ 1158 | #include 1159 | using namespace std; 1160 | 1161 | class Base { 1162 | private: 1163 | int s; 1164 | public: 1165 | int set_value(int a) { // Setter 1166 | s = a; 1167 | } 1168 | 1169 | int get_value() { // Getter 1170 | return s; 1171 | } 1172 | }; 1173 | 1174 | int main() { 1175 | Base base; 1176 | 1177 | base.set_value(5); 1178 | 1179 | cout << "The variable s was set to: " << base.get_value(); // The variable s was set to: 5 1180 | return 0; 1181 | } 1182 | ``` 1183 | 1184 | ### Связь с инкапсуляцией 1185 | 1186 | Геттеры и сеттеры тесно связаны с инкапсуляцией (см. [#Базовые принципы ООП: Инкапсуляция](#инкапсуляция): так как принцип инкапсуляции предполагает скрытие деталей имплементации от пользователя, то для того, чтобы управлять скрытыми данными, требуются специальные паблик методы - 1187 | геттеры и сеттеры. 1188 | 1189 | --- 1190 | 1191 | ## Разделение класса на описание и имплементацию 1192 | 1193 | Класс также можно разделить на описание и имплементацию и подключить его в виде библиотеки. 1194 | Пример: 1195 | 1196 | Содержимое файла **Foo.h**. Описание класса. 1197 | 1198 | ```c++ 1199 | #ifndef OOP_LEARN_FOO_H 1200 | #define OOP_LEARN_FOO_H 1201 | 1202 | class Foo { 1203 | private: 1204 | int s; 1205 | public: 1206 | Foo(); 1207 | 1208 | int set_value(int a); 1209 | 1210 | int get_value(); 1211 | }; 1212 | 1213 | #endif 1214 | ``` 1215 | 1216 | Содержимое файла **Foo.cpp**. Имплементация класса. 1217 | 1218 | ```c++ 1219 | #include 1220 | #include "Foo.h" 1221 | using namespace std; 1222 | 1223 | Foo:: Foo() { 1224 | s = 0; 1225 | cout << "Constructor was called in the Foo header file" << endl; 1226 | } 1227 | 1228 | int Foo::set_value(int a) { 1229 | s = a; 1230 | } 1231 | 1232 | int Foo::get_value() { 1233 | return s; 1234 | } 1235 | ``` 1236 | 1237 | Программа с классом Base, имплементированным в виде библиотеки: 1238 | 1239 | ```c++ 1240 | #include 1241 | #include "Foo.h" 1242 | using namespace std; 1243 | 1244 | int main() { 1245 | Foo foo; // Constructor was called in the Foo header file 1246 | 1247 | foo.set_value(5); 1248 | 1249 | cout << "The variable was set to: " << foo.get_value(); // The variable was set to: 5 1250 | return 0; 1251 | } 1252 | ``` 1253 | 1254 | ### Достоинства данного метода 1255 | 1256 | - Класс можно использовать в различных файлах, просто включив библиотеку в программу, что поможет избежать дублирования кода. 1257 | - От пользователя скрыта имплементация методов, так как в хедере содержится лишь описание класса. Это также помогает другим пользователям класса 1258 | лучше понять написанный код, не отвлекаясь не детали имплементации. 1259 | - Легче проделывать какие-либо изменения в коде, так как для этого потребуется только найти нужные файлы, где имплементирован класс, а не искать среди огромного количества строк кода. 1260 | 1261 | --- 1262 | 1263 | ## RAII 1264 | 1265 | **RAII (Resource Acquisition Is Initialization)** - идиома ООП, заключающаяся в том, что: 1266 | - выделение ресурсов для требуемых действий в классе должно происходить во время инициализации объекта 1267 | - освобождение, соответственно, во время его уничтожения, таким образом привязывая используемые ресурсы к времени жизни объекта 1268 | 1269 | **RAII** - это требование безопасности выделения ресурсов и их последующего освобождения, а также безопасности работы программы в случае исключения. Каждый программист должен писать программы, следуя RAII. 1270 | 1271 | *Ресурс* - это не только выделенная память, а также открытие файлов, установка соединения с сервером и другое. Но все это должно освобождаться в конце жизненного цикла, таким образом исключая утечки и ошибки в работе программе. 1272 | 1273 | ### Действия в случае исключения 1274 | 1275 | Если при инициализации объекта произошла ошибка или исключение, то может произойти утечка памяти, так как некоторые выделенные в динамической памяти члены класса не будут высвобождены. 1276 | 1277 | Пример программы, в которой происходит утечка памяти из-за исключения: 1278 | 1279 | ```c++ 1280 | #include 1281 | using namespace std; 1282 | 1283 | class Foo { 1284 | private: 1285 | int* arr; 1286 | public: 1287 | Foo() { 1288 | arr = new int[3]{1, 2, 3}; // Память, выделенная под массив, не освободится 1289 | throw runtime_error("watch out memory leaks!"); 1290 | } 1291 | }; 1292 | 1293 | int main() { 1294 | try { 1295 | Foo foo; 1296 | } 1297 | catch (exception e) { 1298 | 1299 | } 1300 | } 1301 | ``` 1302 | 1303 | Запустим [Valgrind](https://valgrind.org/): 1304 | 1305 | ```text 1306 | ==4734== LEAK SUMMARY: 1307 | ==4734== definitely lost: 12 bytes in 1 blocks 1308 | ==4734== indirectly lost: 0 bytes in 0 blocks 1309 | ==4734== possibly lost: 0 bytes in 0 blocks 1310 | ``` 1311 | 1312 | Для того, чтобы исправить утечку памяти, следует использовать **[smart pointer](https://en.wikipedia.org/wiki/Smart_pointer)**, который *автоматически* вызывает соответствующий типу указателя оператор высвобождения памяти, когда указатель выходит из области видимости и соответственно при завершении программы ошибкой. 1313 | 1314 | Пример программы, использующей *smart pointer*: 1315 | 1316 | ```c++ 1317 | #include 1318 | #include 1319 | 1320 | using namespace std; 1321 | 1322 | class Foo { 1323 | private: 1324 | unique_ptr arr; 1325 | public: 1326 | Foo() { 1327 | arr = unique_ptr(new int[3]{1, 2, 3}); // Smart pointer 1328 | throw runtime_error("no memory leaks"); 1329 | } 1330 | }; 1331 | 1332 | int main() { 1333 | try { 1334 | Foo foo; 1335 | } 1336 | catch (exception) { 1337 | 1338 | } 1339 | } 1340 | ``` 1341 | 1342 | Запустим [Valgrind](https://valgrind.org/): 1343 | 1344 | ```text 1345 | ==6432== LEAK SUMMARY: 1346 | ==6432== definitely lost: 0 bytes in 0 blocks 1347 | ==6432== indirectly lost: 0 bytes in 0 blocks 1348 | ==6432== possibly lost: 0 bytes in 0 blocks 1349 | ``` 1350 | 1351 | Утечка исчезла, ошибка исправлена. 1352 | 1353 | Следует отметить, что в случае исключения при инициализации будут вызваны деструкторы других объектов, которые были уже инициализированы в конструкторе класса A, но не деструктор самого класса A. 1354 | 1355 | Пример: 1356 | 1357 | ```c++ 1358 | #include 1359 | using namespace std; 1360 | 1361 | class Foo { 1362 | public: 1363 | ~Foo() { 1364 | cout << "Foo's destructor" << endl; 1365 | } 1366 | }; 1367 | 1368 | class Bar { 1369 | private: 1370 | Foo foo; 1371 | public: 1372 | Bar() { 1373 | foo = Foo(); 1374 | 1375 | throw runtime_error("error"); 1376 | } 1377 | 1378 | ~Bar() { 1379 | cout << "Bar's destructor" << endl; 1380 | } 1381 | }; 1382 | 1383 | int main() { 1384 | Bar bar; 1385 | return 0; 1386 | // Foo's destructor 1387 | } 1388 | ``` 1389 | 1390 | --- 1391 | 1392 | ## Smart pointers 1393 | 1394 | **Smart pointer** - это указатель, который автоматически удаляет объект в конце его жизненного цикла (вызывая требуемый оператор `delete`/`delete[]` или же деструктор класса). 1395 | 1396 | Пример автоматического высвобождения памяти: 1397 | 1398 | ```c++ 1399 | #include 1400 | #include 1401 | using namespace std; 1402 | 1403 | int main() { 1404 | unique_ptr ptr(new int[100]); 1405 | 1406 | return 0; 1407 | } 1408 | ``` 1409 | 1410 | Запустим [Valgrind](https://valgrind.org/): 1411 | 1412 | ```text 1413 | ==12154== LEAK SUMMARY: 1414 | ==12154== definitely lost: 0 bytes in 0 blocks 1415 | ==12154== indirectly lost: 0 bytes in 0 blocks 1416 | ==12154== possibly lost: 0 bytes in 0 blocks 1417 | ``` 1418 | 1419 | Существует 2 наиболее используемых умных указателя: **unique_ptr** и **shared_ptr**. 1420 | 1421 | ### Отличия между unique_ptr и shared_ptr 1422 | 1423 | Используя **unique_ptr**, пользователь может создать *только один* указатель на какой-либо объект. 1424 | 1425 | Пример: 1426 | 1427 | ```c++ 1428 | #include 1429 | #include 1430 | using namespace std; 1431 | 1432 | int main() { 1433 | unique_ptr ptr(new int[10]); 1434 | unique_ptr newptr = ptr; // Ошибка 1435 | 1436 | return 0; 1437 | } 1438 | ``` 1439 | 1440 | И напротив, **shared_ptr** же позволяет создать *множество указателей* на объект. 1441 | 1442 | Пример: 1443 | 1444 | ```c++ 1445 | #include 1446 | #include 1447 | using namespace std; 1448 | 1449 | int main() { 1450 | shared_ptr ptr(new int[10]); 1451 | shared_ptr newptr = ptr; // Корректно 1452 | 1453 | return 0; 1454 | } 1455 | ``` 1456 | --------------------------------------------------------------------------------