├── calc.zip ├── images ├── abc.png ├── stl.png ├── tlb.png ├── ascii.png ├── async.png ├── float.jpg ├── float.png ├── koi8.gif ├── mutex.png ├── race.png ├── stack.png ├── ascii8.png ├── barriers.png ├── book_01.jpg ├── book_02.jpg ├── book_03.jpg ├── deadlock.png ├── doxygen.png ├── malloc.png ├── process.png ├── russian.jpg ├── streams.png ├── unicode.png ├── values.png ├── class_ref.png ├── combining.png ├── descriptor.png ├── exceptions.jpg ├── iterators.gif ├── processor.png ├── shared_ptr.png ├── uml-class.png ├── granularity.png ├── philosophers.png ├── uml-aggregation.png ├── uml-association.png ├── uml-composition.png ├── uml-inheritance.png ├── virtual_memory.png └── DiningPhilosophersTable.bmp ├── homework ├── 01 │ ├── numbers.zip │ └── test.py ├── 08 │ └── test.py ├── 02 │ └── test.py ├── 06 │ └── test.cpp ├── 03 │ └── test.cpp ├── 05 │ └── test.cpp ├── 04 │ └── test.cpp └── 07 │ └── test.cpp ├── README.md ├── LICENSE ├── 02.memory.md ├── 01.compilation.md ├── 06.copy_move.md ├── 03.functions.md ├── 05.templates.md ├── 09.threads.md ├── 07.exceptions.md ├── 04.classes.md └── 08.stl.md /calc.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/calc.zip -------------------------------------------------------------------------------- /images/abc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/abc.png -------------------------------------------------------------------------------- /images/stl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/stl.png -------------------------------------------------------------------------------- /images/tlb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/tlb.png -------------------------------------------------------------------------------- /images/ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/ascii.png -------------------------------------------------------------------------------- /images/async.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/async.png -------------------------------------------------------------------------------- /images/float.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/float.jpg -------------------------------------------------------------------------------- /images/float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/float.png -------------------------------------------------------------------------------- /images/koi8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/koi8.gif -------------------------------------------------------------------------------- /images/mutex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/mutex.png -------------------------------------------------------------------------------- /images/race.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/race.png -------------------------------------------------------------------------------- /images/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/stack.png -------------------------------------------------------------------------------- /images/ascii8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/ascii8.png -------------------------------------------------------------------------------- /images/barriers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/barriers.png -------------------------------------------------------------------------------- /images/book_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/book_01.jpg -------------------------------------------------------------------------------- /images/book_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/book_02.jpg -------------------------------------------------------------------------------- /images/book_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/book_03.jpg -------------------------------------------------------------------------------- /images/deadlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/deadlock.png -------------------------------------------------------------------------------- /images/doxygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/doxygen.png -------------------------------------------------------------------------------- /images/malloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/malloc.png -------------------------------------------------------------------------------- /images/process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/process.png -------------------------------------------------------------------------------- /images/russian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/russian.jpg -------------------------------------------------------------------------------- /images/streams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/streams.png -------------------------------------------------------------------------------- /images/unicode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/unicode.png -------------------------------------------------------------------------------- /images/values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/values.png -------------------------------------------------------------------------------- /images/class_ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/class_ref.png -------------------------------------------------------------------------------- /images/combining.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/combining.png -------------------------------------------------------------------------------- /images/descriptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/descriptor.png -------------------------------------------------------------------------------- /images/exceptions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/exceptions.jpg -------------------------------------------------------------------------------- /images/iterators.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/iterators.gif -------------------------------------------------------------------------------- /images/processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/processor.png -------------------------------------------------------------------------------- /images/shared_ptr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/shared_ptr.png -------------------------------------------------------------------------------- /images/uml-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/uml-class.png -------------------------------------------------------------------------------- /homework/01/numbers.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/homework/01/numbers.zip -------------------------------------------------------------------------------- /images/granularity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/granularity.png -------------------------------------------------------------------------------- /images/philosophers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/philosophers.png -------------------------------------------------------------------------------- /images/uml-aggregation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/uml-aggregation.png -------------------------------------------------------------------------------- /images/uml-association.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/uml-association.png -------------------------------------------------------------------------------- /images/uml-composition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/uml-composition.png -------------------------------------------------------------------------------- /images/uml-inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/uml-inheritance.png -------------------------------------------------------------------------------- /images/virtual_memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/virtual_memory.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Друзья, домашние задания я объявляю на лекции, поэтому имею полное право до лекции их изменять. Простите 2 | -------------------------------------------------------------------------------- /images/DiningPhilosophersTable.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtrempoltsev/msu_cpp_lectures/HEAD/images/DiningPhilosophersTable.bmp -------------------------------------------------------------------------------- /homework/08/test.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | def run(command): 4 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 5 | out = process.stdout.readlines() 6 | code = process.wait() 7 | return code, out 8 | 9 | def isPing(line): 10 | line = line.rstrip() 11 | if line == "ping": 12 | return True 13 | if line == "pong": 14 | return False 15 | print("not ping not pong") 16 | quit() 17 | 18 | code, out = run("test") 19 | if code != 0: 20 | print("return value != 0") 21 | quit() 22 | 23 | n = 1 24 | for line in out: 25 | str = line.decode("utf-8").rstrip() 26 | if n == 1: 27 | last = isPing(str) 28 | else: 29 | current = isPing(str) 30 | if last == current: 31 | print("failed") 32 | quit() 33 | last = current 34 | n += 1 35 | 36 | if n < 500000: 37 | print(n, "< 500000") 38 | quit() 39 | 40 | print("ok") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Maksim Trempoltsev 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 | -------------------------------------------------------------------------------- /homework/02/test.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import time 3 | 4 | def run(command): 5 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 6 | out = process.stdout.readlines() 7 | code = process.wait() 8 | return code, out 9 | 10 | def test(command, expected_code, expected_value): 11 | print command 12 | code, out = run(command) 13 | if code != expected_code: 14 | print 'return value', expected_code, '(expected) !=', code 15 | return 16 | i = 0 17 | for line in out: 18 | try: 19 | if line.rstrip() != expected_value[i]: 20 | print expected_value[i], '(expected) !=', line.rstrip() 21 | return 22 | i += 1 23 | except ValueError: 24 | print 'invalid output' 25 | return 26 | except IndexError: 27 | print 'invalid output' 28 | return 29 | if i != len(expected_value): 30 | print 'empty output' 31 | return 32 | print 'ok' 33 | 34 | test('./test "2"', 0, [ '2' ]) 35 | test('./test "-2"', 0, [ '-2' ]) 36 | test('./test "2 + 2"', 0, [ '4' ]) 37 | test('./test "2 + 2 "', 0, [ '4' ]) 38 | test('./test "2 +- 2"', 0, [ '0' ]) 39 | test('./test " 2+-4"', 0, [ '-2' ]) 40 | test('./test "- 4- -4"', 0, [ '0' ]) 41 | test('./test "2-3*4+-5/2"', 0, [ '-12' ]) 42 | test('./test "2-3*4*2+1--2+-5/2"', 0, [ '-21' ]) 43 | test('./test', 1, [ 'error' ]) 44 | test('./test 2 + 3', 1, [ 'error' ]) 45 | test('./test "2/0"', 1, [ 'error' ]) 46 | test('./test "2/"', 1, [ 'error' ]) 47 | test('./test "3 + a"', 1, [ 'error' ]) 48 | -------------------------------------------------------------------------------- /homework/06/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "format.h" 5 | 6 | #define checkEqual(x, y) \ 7 | do { \ 8 | if ((x) != (y)) \ 9 | { \ 10 | std::cout << "at line " << __LINE__ << ": " << (x) << " != " << (y) << '\n'; \ 11 | }; \ 12 | } while(0) 13 | 14 | #define checkThrow(x) \ 15 | do { \ 16 | try { (x); } \ 17 | catch (const std::runtime_error&) { break; } \ 18 | catch (...) {} \ 19 | std::cout << "expected runtime_error at line " << __LINE__ << '\n'; \ 20 | } while(0) 21 | 22 | struct Test 23 | { 24 | }; 25 | 26 | std::ostream& operator<<(std::ostream& out, const Test&) 27 | { 28 | out << "test"; 29 | return out; 30 | } 31 | 32 | int main() 33 | { 34 | checkEqual(format(""), ""); 35 | checkEqual(format("1"), "1"); 36 | checkEqual(format("{1}", 1, 2), "2"); 37 | checkEqual(format("{0}{0}", "a"), "aa"); 38 | checkEqual(format("{0}", Test()), "test"); 39 | const Test test; 40 | checkEqual(format("{0}", test), "test"); 41 | checkEqual(format("{0}", const_cast(test)), "test"); 42 | checkEqual(format("{1} aaa {0}{2} {1}", 1, test, "kek"), "test aaa 1kek test"); 43 | 44 | checkThrow(format("{", 1)); 45 | checkThrow(format("{0", 1)); 46 | checkThrow(format("}", 1)); 47 | checkThrow(format("{1}", 1)); 48 | checkThrow(format("{0}{1}{3}", 1, 2)); 49 | checkThrow(format("{0{}", 1)); 50 | checkThrow(format("{0a}", 1)); 51 | checkThrow(format("0}", 1)); 52 | checkThrow(format("{{0}", 1)); 53 | 54 | std::cout << "done\n"; 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /homework/03/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "matrix.h" 4 | 5 | #define check_equal(x, y) do { if ((x) != y) std::cout << "line " << __LINE__ << ": expected " << y << " got " << (x) << '\n'; } while(0) 6 | #define check(cond) do { if (!(cond)) std::cout << "line " << __LINE__ << ": " << #cond << '\n'; } while(0) 7 | #define check_throw(expr, err) do { try { expr; } catch (const err&) { break ; } catch (...) { } std::cout << "line " << __LINE__ << '\n'; } while(0) 8 | 9 | int main() 10 | { 11 | int n = 0; 12 | 13 | Matrix m1(2, 3); 14 | for (int row = 0; row < 2; ++row) 15 | { 16 | for (int col = 0; col < 3; ++col) 17 | { 18 | m1[row][col] = ++n; 19 | } 20 | } 21 | 22 | check_equal(m1[0][0], 1); 23 | check_equal(m1[0][1], 2); 24 | check_equal(m1[0][2], 3); 25 | check_equal(m1[1][0], 4); 26 | check_equal(m1[1][1], 5); 27 | check_equal(m1[1][2], 6); 28 | 29 | check_equal(m1.getRows(), 2); 30 | check_equal(m1.getColumns(), 3); 31 | 32 | check_throw(m1[0][3], std::out_of_range); 33 | check_throw(m1[2][0], std::out_of_range); 34 | 35 | m1 *= 2; 36 | 37 | const Matrix& m2 = m1; 38 | 39 | check_equal(m2[0][0], 1 * 2); 40 | check_equal(m2[0][1], 2 * 2); 41 | check_equal(m2[0][2], 3 * 2); 42 | check_equal(m2[1][0], 4 * 2); 43 | check_equal(m2[1][1], 5 * 2); 44 | check_equal(m2[1][2], 6 * 2); 45 | 46 | Matrix m3(0, 0); 47 | check_throw(m3[0][0], std::out_of_range); 48 | 49 | const Matrix& m4 = m1; 50 | 51 | Matrix m5(2, 3); 52 | 53 | check(m1 == m1); 54 | check(m1 != m3); 55 | check(m1 == m4); 56 | check(m1 != m5); 57 | 58 | std::cout << "done\n"; 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /homework/05/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "serializer.h" 5 | 6 | #define checkEqual(x, y) do { if ((x) != (y)) { std::cout << "at line " << __LINE__ << ": " << (x) << " != " << (y) << '\n'; }; } while(0) 7 | #define checkTrue(cond) do { if (!(cond)) std::cout << "at line " << __LINE__ << ": " << #cond << '\n'; } while(0) 8 | 9 | struct Data 10 | { 11 | uint64_t a; 12 | bool b; 13 | uint64_t c; 14 | 15 | template 16 | Error serialize(Serializer& serializer) 17 | { 18 | return serializer(a, b, c); 19 | } 20 | }; 21 | 22 | int main() 23 | { 24 | Data x { 1, true, 2 }; 25 | 26 | std::stringstream s1; 27 | 28 | Serializer serializer(s1); 29 | checkTrue(serializer.save(x) == Error::NoError); 30 | 31 | Data y { 0, false, 0 }; 32 | 33 | Deserializer d1(s1); 34 | checkTrue(d1.load(y) == Error::NoError); 35 | 36 | checkEqual(x.a, y.a); 37 | checkEqual(x.b, y.b); 38 | checkEqual(x.c, y.c); 39 | 40 | auto s2 = std::stringstream(""); 41 | Deserializer d2(s2); 42 | checkTrue(d2.load(y) == Error::CorruptedArchive); 43 | 44 | auto s3 = std::stringstream("1 2 3"); 45 | Deserializer d3(s3); 46 | checkTrue(d3.load(y) == Error::CorruptedArchive); 47 | 48 | auto s4 = std::stringstream("1 true -3"); 49 | Deserializer d4(s4); 50 | checkTrue(d4.load(y) == Error::CorruptedArchive); 51 | 52 | auto s5 = std::stringstream("false 1"); 53 | Deserializer d5(s5); 54 | checkTrue(d5.load(y) == Error::CorruptedArchive); 55 | 56 | y = { 0, true, 0 }; 57 | 58 | auto s6 = std::stringstream("100 false 500"); 59 | Deserializer d6(s6); 60 | checkTrue(d6.load(y) == Error::NoError); 61 | 62 | checkEqual(y.a, 100); 63 | checkEqual(y.b, false); 64 | checkEqual(y.c, 500); 65 | 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /homework/01/test.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import time 3 | 4 | def run(command): 5 | process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 6 | out = process.stdout.readlines() 7 | code = process.wait() 8 | return code, out 9 | 10 | def test(command, expected_code, expected_value): 11 | print command 12 | code, out = run(command) 13 | if code != expected_code: 14 | print 'return value', expected_code, '(expected) !=', code 15 | return 16 | if expected_code != 255: 17 | i = 0 18 | for line in out: 19 | try: 20 | if int(line) != expected_value[i]: 21 | print expected_value[i], '(expected) !=', out[0] 22 | return 23 | i += 1 24 | except ValueError: 25 | print 'invalid output' 26 | return 27 | except IndexError: 28 | print 'invalid output' 29 | return 30 | if i != len(expected_value): 31 | print 'empty output' 32 | return 33 | print 'ok' 34 | 35 | test('./test 12 18', 0, [ 1 ]) 36 | test('./test 1 99999', 0, [ 9344 ]) 37 | test('./test 25 25', 0, [ 0 ]) 38 | test('./test 30 29', 0, [ 0 ]) 39 | test('./test 99999 1', 0, [ 0 ]) 40 | test('./test 97 62285', 0, [ 4539 ]) 41 | test('./test 41753 91449', 0, [ 7288 ]) 42 | test('./test 3 99993', 0, [ 9343 ]) 43 | test('./test 3', 255, []) 44 | test('./test', 255, []) 45 | test('./test 3 3 3', 255, []) 46 | test('./test 12 18 1 99999', 0, [ 1, 9344 ]) 47 | 48 | print 'bencmarking' 49 | start = time.time() 50 | run('./test 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999 1 99999') 51 | finish = time.time() 52 | print finish - start, 'sec' 53 | -------------------------------------------------------------------------------- /homework/04/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "bigint.h" 6 | 7 | std::string toString(const BigInt& value) 8 | { 9 | std::stringstream buf; 10 | buf << value; 11 | return buf.str(); 12 | } 13 | 14 | void check(int64_t x, int64_t y) 15 | { 16 | const BigInt bigX = x; 17 | const BigInt bigY = y; 18 | 19 | if (bigX + bigY != BigInt(x + y)) 20 | { 21 | std::cout << x << " + " << y << " != " << x + y << " got " << bigX + bigY << '\n'; 22 | } 23 | 24 | if (bigX - bigY != BigInt(x - y)) 25 | { 26 | std::cout << x << " - " << y << " != " << x - y << " got " << bigX - bigY << '\n'; 27 | } 28 | } 29 | 30 | void doCheckEqual(const BigInt& actual, const char* expected, size_t line) 31 | { 32 | const auto str = toString(actual); 33 | if (str != expected) 34 | { 35 | std::cout << "at line " << line << ": " << str << " != " << expected << '\n'; 36 | } 37 | } 38 | 39 | #define checkEqual(x, y) do { doCheckEqual((x), (y), __LINE__); } while(0) 40 | #define checkTrue(cond) do { if (!(cond)) std::cout << "at line " << __LINE__ << ": " << #cond << '\n'; } while(0) 41 | 42 | int main() 43 | { 44 | BigInt x = 3; 45 | checkEqual(x, "3"); 46 | BigInt y = x; 47 | checkEqual(y, "3"); 48 | BigInt z; 49 | checkEqual(z, "0"); 50 | 51 | checkEqual(BigInt(-10), "-10"); 52 | 53 | checkTrue(x == y); 54 | checkTrue(y == x); 55 | checkTrue(x != z); 56 | checkTrue(z != x); 57 | 58 | z = y; 59 | checkEqual(z, "3"); 60 | 61 | x = 100; 62 | checkEqual(x, "100"); 63 | 64 | checkTrue(!(x < x)); 65 | checkTrue(x < 200); 66 | checkTrue(BigInt(50) < x); 67 | checkTrue(BigInt(-500) < x); 68 | checkTrue(BigInt(-500) < BigInt(-200)); 69 | 70 | checkTrue(!(x > x)); 71 | checkTrue(BigInt(200) > x); 72 | checkTrue(x > BigInt(50)); 73 | checkTrue(x > BigInt(-500)); 74 | checkTrue(BigInt(-200) > BigInt(-500)); 75 | 76 | checkTrue(x <= x); 77 | checkTrue(x <= 200); 78 | checkTrue(BigInt(50) <= x); 79 | checkTrue(BigInt(-500) <= x); 80 | checkTrue(BigInt(-500) <= BigInt(-200)); 81 | 82 | checkTrue(x >= x); 83 | checkTrue(BigInt(200) >= x); 84 | checkTrue(x >= BigInt(50)); 85 | checkTrue(x >= BigInt(-500)); 86 | checkTrue(BigInt(-200) >= BigInt(-500)); 87 | checkTrue(BigInt(0) == -BigInt(0)); 88 | 89 | checkEqual(BigInt(10) + BigInt(10), "20"); 90 | checkEqual(BigInt(-10) + BigInt(10), "0"); 91 | checkEqual(BigInt(10) + BigInt(-10), "0"); 92 | checkEqual(BigInt(-10) + BigInt(-10), "-20"); 93 | 94 | checkEqual(BigInt(10) - BigInt(10), "0"); 95 | checkEqual(BigInt(-10) - BigInt(10), "-20"); 96 | checkEqual(BigInt(10) - BigInt(-10), "20"); 97 | checkEqual(BigInt(-10) - BigInt(-10), "0"); 98 | 99 | checkEqual(BigInt(0) + BigInt(-1), "-1"); 100 | checkEqual(BigInt(0) - BigInt(1), "-1"); 101 | 102 | checkEqual(BigInt(100) - BigInt(100), "0"); 103 | checkEqual(BigInt(99) - BigInt(100), "-1"); 104 | checkEqual(BigInt(10) - BigInt(11), "-1"); 105 | checkEqual(BigInt(20) - BigInt(19), "1"); 106 | 107 | for (int i = -21; i <= 21; ++i) 108 | { 109 | for (int j = -21; j <= 21; ++j) 110 | { 111 | check(i, j); 112 | } 113 | } 114 | 115 | const int64_t step = std::numeric_limits::max() / 99; 116 | const int64_t lower = std::numeric_limits::min() + step; 117 | const int64_t upper = std::numeric_limits::max() - step; 118 | 119 | for (int64_t i = lower; i < upper; i += step) 120 | { 121 | for (int64_t j = -99; j < 99; ++j) 122 | { 123 | check(i, j); 124 | } 125 | } 126 | 127 | const BigInt big1 = std::numeric_limits::max(); 128 | checkEqual(big1, "9223372036854775807"); 129 | 130 | const BigInt big2 = big1 + big1; 131 | checkEqual(big2, "18446744073709551614"); 132 | 133 | const BigInt big3 = big2 - big1; 134 | checkEqual(big3, "9223372036854775807"); 135 | 136 | std::cout << "done\n"; 137 | 138 | return 0; 139 | } 140 | -------------------------------------------------------------------------------- /homework/07/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "vector.h" 9 | 10 | template 11 | int benchmark(T& c) 12 | { 13 | const int N = 1000000; 14 | 15 | int res = 0; 16 | 17 | for (int j = 0; j < 20; ++j) 18 | { 19 | for (int i = 0; i < N / 2; ++i) 20 | { 21 | c.push_back(i + j); 22 | } 23 | 24 | for (int i : c) 25 | { 26 | res += i; 27 | } 28 | 29 | c.resize(N / 4); 30 | 31 | for (int i : c) 32 | { 33 | res += i; 34 | } 35 | 36 | c.resize(N); 37 | 38 | for (int i : c) 39 | { 40 | res += i; 41 | } 42 | 43 | for (int i = 0; i < N / 2; ++i) 44 | { 45 | c.pop_back(); 46 | } 47 | 48 | for (int i : c) 49 | { 50 | res += i; 51 | } 52 | 53 | for (int i = 0; i < N / 2; ++i) 54 | { 55 | c.push_back(i + j); 56 | } 57 | 58 | for (int i : c) 59 | { 60 | res += i; 61 | } 62 | 63 | c.clear(); 64 | } 65 | 66 | return res; 67 | } 68 | 69 | template 70 | void doCheckEqual(const X& actual, const Y& expected, size_t line) 71 | { 72 | if (actual != expected) 73 | { 74 | std::cout << "at line " << line << ": " << actual << " != " << expected << '\n'; 75 | } 76 | } 77 | 78 | #define checkEqual(x, y) do { doCheckEqual((x), (y), __LINE__); } while(0) 79 | #define checkTrue(cond) do { if (!(cond)) std::cout << "at line " << __LINE__ << ": " << #cond << '\n'; } while(0) 80 | 81 | static int Counter = 0; 82 | 83 | struct Counterable 84 | { 85 | Counterable() 86 | { 87 | ++Counter; 88 | } 89 | 90 | Counterable(const Counterable&) 91 | { 92 | ++Counter; 93 | } 94 | 95 | Counterable& operator=(const Counterable&) 96 | { 97 | ++Counter; 98 | return *this; 99 | } 100 | 101 | ~Counterable() 102 | { 103 | --Counter; 104 | } 105 | }; 106 | 107 | class Timer 108 | { 109 | public: 110 | Timer() 111 | : start_(std::chrono::high_resolution_clock::now()) 112 | { 113 | } 114 | 115 | ~Timer() 116 | { 117 | const auto finish = std::chrono::high_resolution_clock::now(); 118 | std::cout << std::chrono::duration_cast(finish - start_).count() << " us" << std::endl; 119 | } 120 | 121 | private: 122 | const std::chrono::high_resolution_clock::time_point start_; 123 | }; 124 | 125 | int main() 126 | { 127 | { 128 | Vector v; 129 | 130 | checkTrue(v.empty()); 131 | checkEqual(v.size(), 0); 132 | 133 | v.push_back(1); 134 | 135 | checkTrue(!v.empty()); 136 | checkEqual(v.size(), 1); 137 | checkEqual(v[0], 1); 138 | 139 | v.pop_back(); 140 | 141 | checkTrue(v.empty()); 142 | checkEqual(v.size(), 0); 143 | 144 | v.push_back(3); 145 | v.push_back(2); 146 | v.push_back(1); 147 | 148 | checkTrue(!v.empty()); 149 | checkEqual(v.size(), 3); 150 | checkEqual(v[0], 3); 151 | checkEqual(v[1], 2); 152 | checkEqual(v[2], 1); 153 | 154 | auto r = v.rbegin(); 155 | checkTrue(r != v.rend()); 156 | checkEqual(*r, 1); 157 | ++r; 158 | checkTrue(r != v.rend()); 159 | checkEqual(*r, 2); 160 | ++r; 161 | checkTrue(r != v.rend()); 162 | checkEqual(*r, 3); 163 | ++r; 164 | checkTrue(r == v.rend()); 165 | 166 | auto f = v.begin(); 167 | checkTrue(f != v.end()); 168 | checkEqual(*f, 3); 169 | ++f; 170 | checkTrue(f != v.end()); 171 | checkEqual(*f, 2); 172 | ++f; 173 | checkTrue(f != v.end()); 174 | checkEqual(*f, 1); 175 | ++f; 176 | checkTrue(f == v.end()); 177 | 178 | v.reserve(10000); 179 | checkEqual(v.size(), 3); 180 | checkTrue(v.capacity() >= 10000); 181 | 182 | const auto c = v.capacity(); 183 | 184 | v.resize(2); 185 | checkEqual(v.size(), 2); 186 | checkEqual(v.capacity(), c); 187 | checkEqual(v[0], 3); 188 | checkEqual(v[1], 2); 189 | 190 | v.resize(3); 191 | checkEqual(v.size(), 3); 192 | checkEqual(v.capacity(), c); 193 | checkEqual(v[0], 3); 194 | checkEqual(v[1], 2); 195 | checkEqual(v[2], 0); 196 | 197 | v.resize(0); 198 | checkEqual(v.size(), 0); 199 | checkTrue(v.begin() == v.end()); 200 | 201 | v.resize(2); 202 | checkEqual(v.size(), 2); 203 | checkEqual(v[0], 0); 204 | checkEqual(v[1], 0); 205 | } 206 | 207 | { 208 | Vector v; 209 | v.resize(100); 210 | 211 | checkEqual(Counter, 100); 212 | 213 | for (int i = 0; i < 100; ++i) 214 | { 215 | v.push_back(Counterable()); 216 | } 217 | 218 | checkEqual(Counter, 200); 219 | 220 | v.resize(150); 221 | 222 | checkEqual(Counter, 150); 223 | 224 | for (int i = 0; i < 100; ++i) 225 | { 226 | v.pop_back(); 227 | } 228 | 229 | checkEqual(Counter, 50); 230 | 231 | v.resize(25); 232 | 233 | checkEqual(Counter, 25); 234 | 235 | v.clear(); 236 | 237 | checkEqual(Counter, 0); 238 | 239 | v.resize(25); 240 | 241 | checkEqual(Counter, 25); 242 | } 243 | 244 | checkEqual(Counter, 0); 245 | 246 | int res = 0; 247 | 248 | { 249 | std::vector v; 250 | res += benchmark(v); 251 | } 252 | 253 | { 254 | std::cout << "Vector: "; 255 | Timer t; 256 | Vector v; 257 | res += benchmark(v); 258 | } 259 | 260 | { 261 | std::cout << "std::vector: "; 262 | Timer t; 263 | std::vector v; 264 | res += benchmark(v); 265 | } 266 | 267 | { 268 | std::cout << "std::deque: "; 269 | Timer t; 270 | std::deque v; 271 | res += benchmark(v); 272 | } 273 | 274 | { 275 | std::cout << "std::list: "; 276 | Timer t; 277 | std::list v; 278 | res += benchmark(v); 279 | } 280 | 281 | return res; 282 | } 283 | -------------------------------------------------------------------------------- /02.memory.md: -------------------------------------------------------------------------------- 1 | # Память в С++ 2 | 3 | ### Кеш, оперативная память, стек и куча, выделение и освобождение памяти 4 | 5 | #### Процессор 6 | 7 | ![](images/processor.png) 8 | 9 | #### Линейное представление памяти 10 | 11 | |Адрес|Значение (1 байт)| 12 | |---|---| 13 | |0x0000|...| 14 | |...|...| 15 | |0x1000|1| 16 | |0x1001|2| 17 | |0x1002|3| 18 | |0x1003|4| 19 | |...|...| 20 | |0xffffffffff|...| 21 | 22 | #### Арифметика указателей 23 | 24 | ```c++ 25 | // Просто хранит какой-то адрес 26 | void* addr = 0x1000; 27 | 28 | // Если указатель никуда не ссылается, 29 | // надо использовать nullptr 30 | void* invalid = nullptr; 31 | 32 | // Размер указателя, например, 4 - это количество 33 | // байт необходимых для размещения адреса 34 | size_t size = sizeof(addr); // size == 4 35 | 36 | // Теперь мы говорим компилятору как 37 | // интерпретировать то, на что указывет 38 | // указатель 39 | char* charPtr = (char*) 0x1000; 40 | 41 | // Разыменование - получение значения, находящегося 42 | // по указанному адресу 43 | char c = *charPtr; // c == 1 44 | 45 | // & - взятие адреса, теперь в charPtrPtr находится 46 | // адрес charPtr 47 | char** charPtrPtr = &charPtr; 48 | 49 | int* intPtr = (int*) addr; 50 | int i = *intPtr; // i == 0x04030201 (little endian) 51 | 52 | int* i1 = intPtr; 53 | int* i2 = i1 + 2; 54 | 55 | ptrdiff_t d1 = i2 - i1; // d1 == 2 56 | 57 | char* c1 = (char*) i1; 58 | char* c2 = (char*) i2; 59 | 60 | ptrdiff_t d2 = c2 - c1; // d2 == 8 61 | ``` 62 | 63 | ``` 64 | T* + n -> T* + sizeof(T) * n 65 | T* - n -> T* - sizeof(T) * n 66 | ``` 67 | 68 | > C-cast использовать в С++ нельзя! Как надо приводить типы в С++ и надо ли вообще будет в другой лекции 69 | 70 | #### Целочисленные типы 71 | 72 | |Знаковые|Беззнаковые| 73 | |---|---| 74 | |char|unsigned char| 75 | |short|unsigned short| 76 | |int|unsigned или unsigned int| 77 | |long|unsigned long| 78 | 79 | > Стандарт не регламентирует размер типов 80 | 81 | ```c++ 82 | #include 83 | ``` 84 | 85 | |Размер, бит|Тип| 86 | |---|---| 87 | |8|int8_t, int_fast8_t, int_least8_t| 88 | |16|int16_t, int_fast16_t, int_least16_t| 89 | |32|int32_t, int_fast32_t, int_least32_t| 90 | |64|int64_t, int_fast64_t, int_least64_t| 91 | 92 | Беззнаковая (unsigned) версия - добавление префикса ```u``` 93 | 94 | ##### mem.cpp 95 | 96 | ```c++ 97 | #include 98 | #include 99 | 100 | int global = 0; 101 | 102 | int main() 103 | { 104 | int* heap = (int*) malloc(sizeof(int)); 105 | 106 | std::cout << std::hex << (uint64_t) main << '\n'; 107 | std::cout << std::hex << (uint64_t) &global << '\n'; 108 | std::cout << std::hex << (uint64_t) heap << '\n'; 109 | std::cout << std::hex << (uint64_t) &heap << '\n'; 110 | 111 | char c; 112 | std::cin >> c; 113 | return 0; 114 | } 115 | ``` 116 | 117 | ``` 118 | g++ -O0 mem.cpp -o mem --std=c++11 119 | ./mem 120 | ``` 121 | 122 | ``` 123 | 400986 124 | 6022b4 125 | 18adc20 126 | 7ffd5591e7d0 127 | ``` 128 | 129 | ##### /proc/.../maps 130 | 131 | ``` 132 | ps ax | grep mem 133 | ``` 134 | 135 | ``` 136 | 00400000-00401000 r-xp 00000000 08:01 2362492 137 | /home/mt/work/tmp/mem 138 | 00601000-00602000 r--p 00001000 08:01 2362492 139 | /home/mt/work/tmp/mem 140 | 00602000-00603000 rw-p 00002000 08:01 2362492 141 | /home/mt/work/tmp/mem 142 | 0189c000-018ce000 rw-p 00000000 00:00 0 143 | [heap] 144 | 7f66aaa53000-7f66aabc5000 r-xp 00000000 08:01 6826866 145 | /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 146 | 7f66aadc5000-7f66aadcf000 r--p 00172000 08:01 6826866 147 | /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 148 | 7f66aadcf000-7f66aadd1000 rw-p 0017c000 08:01 6826866 149 | /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 150 | 7ffd55900000-7ffd55921000 rw-p 00000000 00:00 0 151 | [stack] 152 | 7ffd55952000-7ffd55954000 r--p 00000000 00:00 0 153 | [vvar] 154 | 7ffd55954000-7ffd55956000 r-xp 00000000 00:00 0 155 | [vdso] 156 | ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 157 | [vsyscall] 158 | ``` 159 | 160 | Память разбита на сегменты: 161 | - кода (CS) 162 | - данных (DS) 163 | - стека (SS) 164 | 165 | Регистр сегмента (CS, DS, SS) указывают на дескриптор. 166 | 167 | Для инструкций и стека на смещение в сегменте указывает регистр: 168 | - кода (EIP) 169 | - стека (ESP) 170 | 171 | Линейный адрес - это сумма базового адреса сегмента и смещения. 172 | 173 | #### Дескриптор 174 | 175 | ![](images/descriptor.png) 176 | 177 | Segment limit (20 bit) - размер сегмента, 55-й бит G определяет гранулярность размера: 178 | - байты, если 0 179 | - страницы, если 1 (размер страницы обычно 4Кб) 180 | 181 | Бит 41-43: 182 | - 000 - сегмент данных, только чтение 183 | - 001 - сегмент данных, чтение и запись 184 | - 010 - сегмент стека, только чтение 185 | - 011 - сегмент стека, чтение и запись 186 | - 100 - сегмент кода, только выполнение 187 | - 101- сегмент кода, чтение и выполнение 188 | 189 | #### Виртуальная память 190 | 191 | ![](images/virtual_memory.png) 192 | 193 | - Память делится на страницы 194 | - Страница может находится в оперативной памяти или на внешнем носителе 195 | - Трансляция из физического адреса в виртуальный и обратно выполняется через специальные таблицы: PGD (Page Global Directory), PMD (Page Middle Directory) и PTE (Page Table Entry). В PTE хранятся физические адреса страниц 196 | - Для ускорения трансляции адресов процессор хранит в кеше таблицу TLB (Translation lookaside buffer) 197 | - Если обращение к памяти не может быть оттранслировано через TLB, процессор обращается к таблицам страниц и пытается загрузить PTE оттуда в TLB. Если загрузка не удалась, процессор вызывает прерывание Page Fault 198 | - Обработчик прерывания Page Fault находится в подсистеме виртуальной памяти ядра ОС и может загрузить требуемую страницу с внешнего носителя в оперативную память 199 | 200 | ![](images/tlb.png) 201 | 202 | #### Важные константы 203 | 204 | ``` 205 | 1 такт = 1 / частота процессора 206 | 1 / 3 GHz = 0.3 ns 207 | 0.3 ns 208 | L1 cache reference 0.5 ns 209 | Branch mispredict 5 ns 210 | ``` 211 | > Неудачный if () 212 | ``` 213 | L2 cache reference 7 ns 214 | Mutex lock/unlock 25 ns 215 | Main memory reference 100 ns 216 | ``` 217 | > Кроме задержки (latency) есть понятие пропускной способности (throughput, bandwidth). В случае чтения из RAM - 10-50 Gb/sec 218 | ``` 219 | Compress 1K bytes with Zippy 3,000 ns 220 | Send 1K bytes over 1 Gbps network 10,000 ns 221 | Read 4K randomly from SSD 150,000 ns 222 | Read 1 MB sequentially from memory 250,000 ns 223 | Round trip within same datacenter 500,000 ns 224 | Read 1 MB sequentially from SSD 1,000,000 ns 225 | HDD seek 10,000,000 ns 226 | Read 1 MB sequentially from HDD 20,000,000 ns 227 | Send packet CA->Netherlands->CA 150,000,000 ns 228 | ``` 229 | Источник: [https://gist.github.com/jboner/2841832](https://gist.github.com/jboner/2841832) 230 | 231 | Иллюстрация: [https://github.com/Kobzol/hardware-effects](https://github.com/Kobzol/hardware-effects) 232 | 233 | ### Выводы из таблицы 234 | 1. Стараться укладывать данные в кеш 235 | 2. Минимизировать скачки по памяти 236 | 3. В условиях основной веткой делать ветку которая выполняется с большей вероятностью 237 | 238 | #### Стек 239 | 240 | ![](images/stack.png) 241 | 242 | ### Классы управления памятью и областью видимости в C++ 243 | 244 | Характеризуются тремя понятиями: 245 | 1. **Время жизни** 246 | > Продолжительность хранения данных в памяти 247 | 2. **Область видимости** 248 | > Части кода из которых можно получить доступ к данным 249 | 3. **Связывание (linkage)** 250 | > Если к данным можно обратиться из другой единицы трансляции — связывание внешнее (external), иначе связывание внутреннее (internal) 251 | 252 | #### Автоматический/регистровый (register) 253 | 254 | | Время жизни | Область видимости | Связывание | 255 | | --- | --- | --- | 256 | | Автоматическое (блок) | Блок | Отсутствует | 257 | 258 | ```c++ 259 | { 260 | int i = 5; 261 | } 262 | 263 | if (true) 264 | { 265 | register int j = 3; 266 | } 267 | 268 | for (int k = 0; k < 7; ++k) 269 | { 270 | } 271 | ``` 272 | 273 | #### Статический без связывания 274 | 275 | | Время жизни | Область видимости | Связывание | 276 | | --- | --- | --- | 277 | | Статическое | Блок | Отсутствует | 278 | 279 | ```c++ 280 | void foo() 281 | { 282 | static int j = 3; 283 | } 284 | ``` 285 | 286 | > Инициализируется при первом обращении 287 | 288 | #### Статический с внутренним связыванием 289 | 290 | | Время жизни | Область видимости | Связывание | 291 | | --- | --- | --- | 292 | | Статическое | Файл | Внутреннее | 293 | 294 | ```c++ 295 | static int i = 5; 296 | ``` 297 | 298 | > Инициализируется до входа в main 299 | 300 | #### Статический с внешним связыванием 301 | 302 | | Время жизни | Область видимости | Связывание | 303 | | --- | --- | --- | 304 | | Статическое | Файл | Внешнее | 305 | 306 | ```c++ 307 | // *.cpp 308 | int i = 0; 309 | ``` 310 | ```c++ 311 | // *.h 312 | extern int i; 313 | ``` 314 | 315 | ### Типы памяти 316 | 317 | #### Стек (Stack) 318 | 319 | ```c++ 320 | int i = 5; 321 | std::string name; 322 | char data[5]; 323 | ``` 324 | > Выделение памяти на стеке очень быстрая, но стек не резиновый 325 | 326 | #### Куча (Heap) 327 | 328 | ```c++ 329 | int* i = (int*) malloc(sizeof(int)); 330 | std::string* name = new std::string(); 331 | char* data = new char[5]; 332 | ... 333 | free(i); 334 | delete(name); 335 | delete[] data; 336 | ``` 337 | Память в куче выделяют new и malloc, есть сторонние менеджеры памяти. 338 | 339 | ![](images/malloc.png) 340 | 341 | Основное: 342 | - new то же, что и malloc, только дополнительно вызывает конструкторы 343 | - Внутри malloc есть буфер, если в буфере есть место, ваш вызов может выполниться быстро 344 | - Если памяти в буфере нет, будет запрошена память у ОС (sbrk, VirtualAlloc) - это дорого 345 | - ОС выделяет память страницами от 4Кб, а может быть и все 2Мб 346 | - Стандартные аллокаторы универсальные, то есть должны быть потокобезопасны, быть одинаково эффективны для блоков разной длины, и 10 байт и 100Мб. Плата за универсальность - быстродействие 347 | 348 | #### valgrind 349 | 350 | ```c++ 351 | #include 352 | 353 | int main() 354 | { 355 | int* data = (int*) malloc(1024); 356 | return 0; 357 | } 358 | ``` 359 | 360 | ``` 361 | valgrind ./mem 362 | ``` 363 | 364 | ``` 365 | ==117392== Memcheck, a memory error detector 366 | ==117392== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 367 | ==117392== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info 368 | ==117392== Command: ./mem 369 | ==117392== 370 | ==117392== 371 | ==117392== HEAP SUMMARY: 372 | ==117392== in use at exit: 1,024 bytes in 1 blocks 373 | ==117392== total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated 374 | ==117392== 375 | ==117392== LEAK SUMMARY: 376 | ==117392== definitely lost: 1,024 bytes in 1 blocks 377 | ==117392== indirectly lost: 0 bytes in 0 blocks 378 | ==117392== possibly lost: 0 bytes in 0 blocks 379 | ==117392== still reachable: 0 bytes in 0 blocks 380 | ==117392== suppressed: 0 bytes in 0 blocks 381 | ==117392== Rerun with --leak-check=full to see details of leaked memory 382 | ==117392== 383 | ==117392== For counts of detected and suppressed errors, rerun with: -v 384 | ==117392== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 385 | ``` 386 | 387 | #### Глобальная память (data segment) 388 | 389 | ```c++ 390 | static const int i = 5; 391 | static std::string name; 392 | extern char data[5]; 393 | ``` 394 | > Если не удастся разместить блок глобальной памяти, то программа даже не запустится 395 | 396 | ### Массивы 397 | 398 | ```c++ 399 | T array[maxColumns]; 400 | T value = array[x]; 401 | ``` 402 | 403 | > Значение в квадратных скобках должно быть известно на этапе компиляции, увы 404 | 405 | ```c++ 406 | int data[] = { 1, 2, 3 }; 407 | int i = data[2]; 408 | ``` 409 | 410 | Фактически - это вычисление смещения: 411 | 412 | ```c++ 413 | ptr = data; 414 | ptr = ptr + 2 * sizeof(int); 415 | i = *ptr; 416 | ``` 417 | 418 | Массив - непрерывный блок байт в памяти, sizeof(data) вернет размер массива в байтах (не элементах!). Размер массива в элементах можно вычислить: sizeof(data) / sizeof(data[0]) 419 | 420 | ```c++ 421 | int* data = new int[10]; 422 | int i = data[2]; 423 | delete[] data; 424 | ``` 425 | 426 | ##### Массив <-> указатель 427 | 428 | ```c++ 429 | int i[] = { 1, 2, 3 }; 430 | int* j = i; 431 | using array = int*; 432 | array k = (array) j; 433 | ``` 434 | 435 | ### Двумерные массивы 436 | 437 | ```c++ 438 | T array[maxRows][maxColumns]; 439 | T value = array[y][x]; 440 | ``` 441 | 442 | ```c++ 443 | int data[][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; 444 | int i = data[2][1]; 445 | ``` 446 | 447 | Фактически: 448 | 449 | ```c++ 450 | ptr = data; 451 | ptr = ptr + maxColumns * sizeof(int) * 2 + 1; 452 | i = *ptr; 453 | ``` 454 | 455 | ##### Массив <-> указатель 456 | ```c++ 457 | int i[][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; 458 | int* j = (int*) i; 459 | using matrix = int(*)[2]; 460 | matrix k = (matrix) j; 461 | ``` 462 | 463 | ### Измеряем скорость работы (benchmark) 464 | 465 | 1. Измерений должно быть много 466 | 2. Одному прогону верить нельзя 467 | 3. Компилятор оптимизирует, надо ему мешать 468 | 4. Перед тестами надо греться 469 | 470 | ##### Пример "вредной" оптимизации 471 | 472 | ```c++ 473 | int main() 474 | { 475 | for (int i = 0; i < 100 * 1000 * 1000; ++i) 476 | int a = i / 3; 477 | return 0; 478 | } 479 | ``` 480 | 481 | ##### Не даем компилятору оптимизировать 482 | 483 | ```c++ 484 | int main() 485 | { 486 | for (int i = 0; i < 100 * 1000 * 1000; ++i) 487 | volatile int a = i / 3; 488 | return 0; 489 | } 490 | ``` 491 | 492 | ### Домашнее задание 493 | 494 | Написать свой аллокатор со стратегией линейного выделения памяти со следующим интерфейсом: 495 | 496 | ```c++ 497 | void makeAllocator(size_t maxSize); 498 | char* alloc(size_t size); 499 | void reset(); 500 | ``` 501 | 502 | При вызове makeAllocator аллоцируется указанный размер, после чего при вызове alloc возвращает указатель на блок запрошенного размера или nullptr, если места недостаточно. После вызова reset аллокатор позволяет использовать свою память снова. 503 | 504 | ### Правила оформления домашних заданий 505 | 506 | 1. В вашем github должен быть репозиторий msu_cpp_spring_2020 507 | 2. Внутри репозитория должны быть директории из двух цифр, вида: 00, 01, 02 и т.д. - это номера домашних заданий. Это задание имеет номер 01 508 | 3. Внутри каждой директории могут быть любые файлы реализующие задачу. Обязательным является только файл Makefile 509 | 4. В Makefile обязательно должны быть цель test, которая запускает тесты вашего решения 510 | 5. Собираться ваш код должен компилятором С++ поддерживающим 14 стандарт 511 | 6. Внешних зависимостей быть не должно 512 | 513 | EOF 514 | -------------------------------------------------------------------------------- /01.compilation.md: -------------------------------------------------------------------------------- 1 | ## Лекции 2 | 3 | [https://github.com/mtrempoltsev/msu_cpp_lectures](https://github.com/mtrempoltsev/msu_cpp_lectures) 4 | 5 | ## Рекомендуемая литература 6 | 7 | ### Начальный уровень 8 | 9 | ![](images/book_01.jpg) 10 | 11 | #### Брюс Эккель, Философия С++ 12 | 13 | Книга старая, но довольно основательная. 14 | 15 | ### Продвинутый уровень 16 | 17 | 1. Стивен Дьюхерст, C++. Священные знания 18 | 2. Скотт Мейерс, смело можно читать все 19 | 3. Герб Саттер, аналогично 20 | 21 | ### Из относительно свежего 22 | 23 | ![](images/book_02.jpg) 24 | ![](images/book_03.jpg) 25 | 26 | ## Препроцессор, компилятор, компоновщик 27 | 28 | Процесс трансляции исходного кода в виде текстового файла в представление, которое может быть выполнено процессором - сборка. 29 | 30 | Состоит из 3 этапов: 31 | 1. Обработка исходного кода препроцессором (preprocessing) 32 | 2. Компиляция, то есть перевод подготовленного исходного кода в инструкции процессора (объектный файл) (compiling) 33 | 3. Компоновка - сборка одного или нескольких объектных файлов в один исполняемый файл (linking) 34 | 35 | ##### square.cpp 36 | 37 | ```c++ 38 | int square(int value) 39 | { 40 | return value * value; 41 | } 42 | ``` 43 | 44 | Это файл с исходным кодом, он содержит определения функций. 45 | 46 | Компилируются cpp/c/etc файлы, один файл с исходным кодом - один объектный файл. **Это называется единица трансляции.** 47 | 48 | > Удобный инструмент: [https://godbolt.org](https://godbolt.org) 49 | 50 | ``` 51 | g++ -c square.cpp 52 | ``` 53 | 54 | Вывод: 55 | 56 | ``` 57 | square.o 58 | ``` 59 | 60 | ``` 61 | objdump -d square.o 62 | ``` 63 | 64 | ```nasm 65 | square.o: file format elf64-x86-64 66 | 67 | Disassembly of section .text: 68 | 69 | 0000000000000000 <_Z6squarei>: 70 | 0: 55 push %rbp 71 | 1: 48 89 e5 mov %rsp,%rbp 72 | 4: 89 7d fc mov %edi,-0x4(%rbp) 73 | 7: 8b 45 fc mov -0x4(%rbp),%eax 74 | a: 0f af 45 fc imul -0x4(%rbp),%eax 75 | e: 5d pop %rbp 76 | f: c3 retq 77 | ``` 78 | 79 | #### Секции 80 | 81 | Блоки данных в откомпилированном файле. Это может быть: 82 | - Код (.text) 83 | - Статические данные (.data) 84 | - Таблицы строк 85 | - Таблицы символов (.symtab) 86 | 87 | #### Символы 88 | 89 | То, что находится в объектном файле - кортежи из имени, адреса и свойств: 90 | 91 | - Имя - произвольная строка 92 | - Адрес - число (смещение, адрес) 93 | - Свойства 94 | 95 | #### Декорирование (mangling) 96 | 97 | В С++ есть перегрузка функций (а еще есть классы), поэтому нужен механизм, чтобы различать перегруженные функции. 98 | 99 | ```c++ 100 | void print(int value); // _Z5printi 101 | void print(const char* value); // _Z5printPKc 102 | ``` 103 | 104 | Инструмент для обратного преобразования: 105 | 106 | ``` 107 | c++filt _Z5printPKc 108 | ``` 109 | 110 | ``` 111 | print(char const*) 112 | ``` 113 | 114 | #### extern "C" 115 | 116 | ```c++ 117 | extern "C" 118 | { 119 | void print(int value); // print 120 | } 121 | ``` 122 | 123 | ##### main.cpp 124 | 125 | ```c++ 126 | int square(int value); 127 | 128 | int main() 129 | { 130 | return square(2); 131 | } 132 | ``` 133 | 134 | ``` 135 | objdump -d -r main.o 136 | ``` 137 | 138 | ```nasm 139 | 0000000000000000
: 140 | 0: 55 push %rbp 141 | 1: 48 89 e5 mov %rsp,%rbp 142 | 4: bf 02 00 00 00 mov $0x2,%edi 143 | 9: e8 00 00 00 00 callq e 144 | a: R_X86_64_PC32 _Z6squarei-0x4 145 | e: 5d pop %rbp 146 | f: c3 retq 147 | ``` 148 | 149 | > ```-r``` - информация о релокациях 150 | 151 | #### Символы 152 | 153 | Символ - кортеж из имени, адреса и свойств: 154 | 155 | - Имя - произвольная строка 156 | - Адрес - число (смещение, адрес) 157 | - Свойства 158 | 159 | Связывание (binding) - говорит о том, виден ли символ вне файла: 160 | 161 | - Локальный символ 162 | - Глобальный символ 163 | - Внешний символ 164 | 165 | Смотрим таблицу символов: 166 | 167 | ``` 168 | objdump -t square.o 169 | ``` 170 | 171 | Вывод: 172 | 173 | ``` 174 | square.o: file format elf64-x86-64 175 | 176 | SYMBOL TABLE: 177 | 0000000000000000 l df *ABS* 0000000000000000 square.cpp 178 | 0000000000000000 l d .text 0000000000000000 .text 179 | 0000000000000000 l d .data 0000000000000000 .data 180 | 0000000000000000 l d .bss 0000000000000000 .bss 181 | 0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack 182 | 0000000000000000 l d .eh_frame 0000000000000000 .eh_frame 183 | 0000000000000000 l d .comment 0000000000000000 .comment 184 | 0000000000000000 g F .text 0000000000000010 _Z6squarei 185 | ``` 186 | 187 | Первая колонка - связывание: 188 | 189 | - l - локальное 190 | - g - глобальное 191 | - пробел - ни один из вариантов 192 | 193 | Седьмая колонка - тип, если стоит ```F``` - значит это функция. 194 | 195 | ``` 196 | readelf -s square.o 197 | ``` 198 | 199 | ``` 200 | Symbol table '.symtab' contains 9 entries: 201 | Num: Value Size Type Bind Vis Ndx Name 202 | 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 203 | 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS square.cpp 204 | 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 205 | 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 206 | 4: 0000000000000000 0 SECTION LOCAL DEFAULT 3 207 | 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 208 | 6: 0000000000000000 0 SECTION LOCAL DEFAULT 6 209 | 7: 0000000000000000 0 SECTION LOCAL DEFAULT 4 210 | 8: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z6squarei 211 | ``` 212 | 213 | ``` 214 | objdump -t main.o 215 | ``` 216 | 217 | ``` 218 | main.o: file format elf64-x86-64 219 | 220 | SYMBOL TABLE: 221 | 0000000000000000 l df *ABS* 0000000000000000 main.cpp 222 | 0000000000000000 l d .text 0000000000000000 .text 223 | 0000000000000000 l d .data 0000000000000000 .data 224 | 0000000000000000 l d .bss 0000000000000000 .bss 225 | 0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack 226 | 0000000000000000 l d .eh_frame 0000000000000000 .eh_frame 227 | 0000000000000000 l d .comment 0000000000000000 .comment 228 | 0000000000000000 g F .text 0000000000000010 main 229 | 0000000000000000 *UND* 0000000000000000 _Z6squarei 230 | ``` 231 | 232 | ``` 233 | readelf -s main.o 234 | ``` 235 | 236 | ``` 237 | Symbol table '.symtab' contains 10 entries: 238 | Num: Value Size Type Bind Vis Ndx Name 239 | 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 240 | 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.cpp 241 | 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 242 | 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 243 | 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 244 | 5: 0000000000000000 0 SECTION LOCAL DEFAULT 6 245 | 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 246 | 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 247 | 8: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 main 248 | 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z6squarei 249 | ``` 250 | 251 | ##### main.cpp 252 | 253 | ```c++ 254 | int square(int value); 255 | 256 | int main() 257 | { 258 | return square(2); 259 | } 260 | ``` 261 | 262 | ##### square.h 263 | 264 | ```c++ 265 | int square(int value); 266 | ``` 267 | 268 | Это заголовочный файл, как правило в нем находятся объявления типов и функций. 269 | 270 | ##### main.cpp 271 | 272 | ```c++ 273 | #include "square.h" 274 | 275 | int main() 276 | { 277 | return square(2); 278 | } 279 | ``` 280 | 281 | ### Препроцессор 282 | 283 | ``` 284 | g++ -E main.cpp 285 | ``` 286 | 287 | Вывод: 288 | 289 | ```c++ 290 | # 1 "main.cpp" 291 | # 1 "" 292 | # 1 "" 293 | # 1 "/usr/include/stdc-predef.h" 1 3 4 294 | # 1 "" 2 295 | # 1 "main.cpp" 296 | # 1 "square.h" 1 297 | int square(int value); 298 | # 2 "main.cpp" 2 299 | 300 | int main() 301 | { 302 | return square(2); 303 | } 304 | ``` 305 | 306 | Директивы препроцессора: 307 | 308 | - ```#include "name"``` - целиком вставляет файл с именем ```name```, вставляемый файл также обрабатывается препроцессором. Поиск файла происходит в директории с файлом, из которого происходит включение 309 | - ```#include ``` - аналогично предыдущей директиве, но поиск производится в глобальных директориях и директориях, указанных с помощью ключа ```-I``` 310 | - ```#define x y``` - вместо ```x``` подставляет ```y``` 311 | 312 | > define - это опасно 313 | 314 | ```c++ 315 | #define true false // happy debugging 316 | #define true !!(rand() % 2) 317 | ``` 318 | 319 | #### Условная компиляция 320 | 321 | ``` 322 | g++ -DDEBUG main.cpp 323 | ``` 324 | 325 | ```c++ 326 | #define DEBUG 327 | #ifdef DEBUG 328 | ... 329 | #else 330 | ... 331 | #endif 332 | ``` 333 | 334 | ### Компиляция 335 | 336 | ``` 337 | g++ -c main.cpp 338 | ``` 339 | 340 | В результате мы имеем 2 файла: 341 | - main.o 342 | - square.o 343 | 344 | ### Компоновка 345 | 346 | ``` 347 | g++ -o my_prog main.o square.o 348 | ``` 349 | 350 | Вывод: 351 | 352 | ``` 353 | my_prog 354 | ``` 355 | 356 | ``` 357 | ./my_prog 358 | echo $? 359 | 4 360 | ``` 361 | 362 | Компоновщик собирает из одного и более объектных файлов исполняемый файл. 363 | 364 | ### Что g++ делает под капотом 365 | 366 | ``` 367 | g++ -o my_prog -v main.cpp square.cpp 368 | ``` 369 | 370 | Вывод: 371 | 372 | ``` 373 | ... 374 | 375 | /usr/lib/gcc/x86_64-linux-gnu/5/cc1plus 376 | main.cpp -o /tmp/ccjBvzkg.s 377 | 378 | ... 379 | 380 | as -v --64 -o /tmp/ccM2mLyf.o /tmp/ccjBvzkg.s 381 | 382 | ... 383 | 384 | /usr/lib/gcc/x86_64-linux-gnu/5/cc1plus 385 | square.cpp -o /tmp/ccjBvzkg.s 386 | 387 | ... 388 | 389 | as -v --64 -o /tmp/cc3ZpAQe.o /tmp/ccjBvzkg.s 390 | 391 | ... 392 | 393 | /usr/lib/gcc/x86_64-linux-gnu/5/collect2 394 | /tmp/ccM2mLyf.o /tmp/cc3ZpAQe.o 395 | -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc 396 | ``` 397 | 398 | ### Оптимизация 399 | 400 | ##### main.cpp 401 | 402 | ```c++ 403 | int square(int value) 404 | { 405 | return value * value; 406 | } 407 | 408 | int main() 409 | { 410 | return square(2); 411 | } 412 | ``` 413 | 414 | ``` 415 | g++ -c main.cpp 416 | objdump -d main.o 417 | ``` 418 | 419 | ```nasm 420 | 0000000000000010
: 421 | 10: 55 push %rbp 422 | 11: 48 89 e5 mov %rsp,%rbp 423 | 14: bf 02 00 00 00 mov $0x2,%edi 424 | 19: e8 00 00 00 00 callq 1e 425 | 1e: 5d pop %rbp 426 | 1f: c3 retq 427 | ``` 428 | 429 | ``` 430 | g++ -O2 -c main.cpp 431 | objdump -d main.o 432 | ``` 433 | 434 | ```nasm 435 | 0000000000000000
: 436 | 0: b8 04 00 00 00 mov $0x4,%eax 437 | 5: c3 retq 438 | ``` 439 | 440 | ### Статические библиотеки 441 | 442 | 443 | 444 | ``` 445 | ar rc libsquare.a squre.o 446 | ``` 447 | 448 | Вывод: 449 | 450 | ``` 451 | libsquare.a 452 | ``` 453 | 454 | > В unix принято, что статические библиотеки имеют префикс lib и расширение .a 455 | 456 | ``` 457 | g++ -o my_prog main.o -L. -lsquare 458 | ``` 459 | 460 | ```-L``` - путь в котором компоновщик будет искать библиотеки 461 | ```-l``` - имя библиотеки 462 | 463 | > Статические библиотеки нужны только при сборке 464 | 465 | 466 | ### Ошибки при сборке 467 | 468 | 1. Компиляции 469 | 2. Компоновки 470 | 471 | ### Ошибки компоновки 472 | 473 | #### Компоновщик не может найти символ 474 | 475 | ``` 476 | g++ -c math.cpp 477 | g++ -o my_prog main.o 478 | ``` 479 | 480 | ``` 481 | main.o: In function `main': 482 | main.cpp:(.text+0xa): undefined reference to `square(int)' 483 | collect2: error: ld returned 1 exit status 484 | ``` 485 | 486 | ##### Что делать? 487 | 488 | Включить необходимый файл в сборку, если нет определения символа - написать его, проверить, что файлы созданы одинаковой версией компилятора и с одними опциями компиляции. 489 | 490 | #### Символ встретился несколько раз - компоновщик не знает какую версию выбрать 491 | 492 | ##### math.cpp 493 | 494 | ```c++ 495 | int square(int value) 496 | { 497 | return value * value; 498 | } 499 | ``` 500 | 501 | ``` 502 | g++ -c math.cpp 503 | g++ -o my_prog main.o square.o math.o 504 | ``` 505 | 506 | ``` 507 | math.o: In function `square(int)': 508 | math.cpp:(.text+0x0): multiple definition of `square(int)' 509 | square.o:square.cpp:(.text+0x0): first defined here 510 | collect2: error: ld returned 1 exit status 511 | ``` 512 | 513 | ##### Что делать? 514 | 515 | Убрать неоднозначность: переименовать одну из функций, поместить в другое пространство имен, изменить видимость и т.д. 516 | 517 | #### Рекомендуемое разделение на заголовочные файлы и файлы с реализацией 518 | 519 | ##### a.h 520 | 521 | ```c++ 522 | #pragma once 523 | 524 | struct A 525 | { 526 | void foo(); 527 | }; 528 | ``` 529 | 530 | ##### a.cpp 531 | 532 | ```c++ 533 | #include "a.h" 534 | 535 | void A::foo() 536 | { 537 | } 538 | ``` 539 | 540 | #### Защита от повторного включения 541 | 542 | ##### buffer.h 543 | 544 | ```c++ 545 | class Buffer 546 | { 547 | ... 548 | }; 549 | ``` 550 | 551 | ##### text_processor.h 552 | 553 | ```c++ 554 | #include "buffer.h" 555 | ... 556 | ``` 557 | 558 | ##### main.cpp 559 | 560 | ```c++ 561 | #include "buffer.h" 562 | #include "text_processor.h" 563 | ``` 564 | 565 | В одной единице трансляции два объявления класса ```Buffer```, компилятор не знает какое использовать. 566 | 567 | ##### buffer.h 568 | 569 | ```c++ 570 | #ifndef BUFFER_H 571 | #define BUFFER_H 572 | 573 | class Buffer 574 | { 575 | ... 576 | }; 577 | 578 | #endif 579 | ``` 580 | 581 | > Или просто ```#pragma once``` 582 | 583 | #### Циклическое включение 584 | 585 | ##### a.h 586 | 587 | ```c++ 588 | #include "b.h" 589 | 590 | class A 591 | { 592 | B* b; 593 | }; 594 | ``` 595 | 596 | ##### b.h 597 | 598 | ```c++ 599 | #include "a.h" 600 | 601 | class B 602 | { 603 | A* a; 604 | }; 605 | ``` 606 | 607 | #### Предварительное объявление (forward declarations) 608 | 609 | ##### a.h 610 | 611 | ```c++ 612 | class B; 613 | 614 | class A 615 | { 616 | B* b; 617 | }; 618 | ``` 619 | 620 | ##### a.cpp 621 | 622 | ```c++ 623 | #include "b.h" 624 | #include "a.h" 625 | 626 | ... 627 | ``` 628 | 629 | ##### b.h 630 | 631 | ```c++ 632 | class A; 633 | 634 | class B 635 | { 636 | A* a; 637 | }; 638 | ``` 639 | 640 | ### make 641 | 642 | Утилита для автоматизации. 643 | 644 | Синтаксис: 645 | 646 | ``` 647 | цель: зависимости 648 | [tab] команда 649 | ``` 650 | 651 | Скрипт как правило находится в файле с именем **Makefile**. 652 | 653 | Вызов: 654 | 655 | ``` 656 | make цель 657 | ``` 658 | 659 | Цель `all` вызывается, если явно не указать цель: 660 | 661 | ``` 662 | make 663 | ``` 664 | 665 | #### Плохой вариант 666 | 667 | ##### Makefile 668 | 669 | ```make 670 | CC=g++ 671 | 672 | all: my_prog 673 | 674 | my_prog: main.cpp square.cpp square.h 675 | $(CC) -o my_prog main.cpp square.cpp 676 | 677 | clean: 678 | rm -rf *.o my_prog 679 | ``` 680 | 681 | #### Хороший вариант 682 | 683 | ##### Makefile 684 | 685 | ```make 686 | CC=g++ 687 | 688 | all: my_prog 689 | 690 | my_prog: main.o square.o 691 | $(CC) -o my_prog main.o square.o 692 | 693 | main.o: main.cpp square.h 694 | $(CC) -c main.cpp 695 | 696 | square.o: square.cpp square.h 697 | $(CC) -c square.cpp 698 | 699 | clean: 700 | rm -rf *.o my_prog 701 | ``` 702 | 703 | EOF 704 | -------------------------------------------------------------------------------- /06.copy_move.md: -------------------------------------------------------------------------------- 1 | ### Argument-dependent name lookup (ADL) 2 | 3 | Известен также, как Koenig lookup. 4 | 5 | ```c++ 6 | namespace X 7 | { 8 | struct A { ... }; 9 | 10 | std::ostream& operator<<( 11 | std::ostream& out, const A& value) { ... } 12 | 13 | void foo(const A& value) { ... } 14 | } 15 | 16 | X::A a; 17 | 18 | std::cout << a; 19 | foo(a); 20 | ``` 21 | 22 | Компилятор ищет функцию в текущем пространстве имен и если не находит, то в пространствах имен аргументов. Если находит подходящую функцию в двух местах, то возникает ошибка. 23 | 24 | ### Методы генерируемые компилятором неявно 25 | 26 | ```c++ 27 | struct A 28 | { 29 | X x; 30 | Y y; 31 | // Конструктор 32 | A() 33 | : x(X()) 34 | , y(Y()) 35 | {} 36 | // Деструктор 37 | ~A() 38 | {} 39 | // Копирующий конструктор 40 | // A a1; 41 | // A a2 = a1; 42 | A(const A& copied) 43 | : x(copied.x) 44 | , y(copied.y) 45 | {} 46 | // Оператор копирования 47 | // A a1; 48 | // A a2; 49 | // a2 = a1; 50 | A& operator=(const A& copied) 51 | { 52 | x = copied.x; 53 | y = copied.y; 54 | return *this; 55 | } 56 | // Перемещающий конструктор 57 | // A a1; 58 | // A a2 = std::move(a1); 59 | A(A&& moved) 60 | : x(std::move(moved.x)) 61 | , y(std::move(moved.y)) 62 | {} 63 | // Оператор перемещения 64 | // A a1; 65 | // A a2; 66 | // a2 = std::move(a1); 67 | A& operator=(A&& moved) 68 | { 69 | x = std::move(moved.x); 70 | y = std::move(moved.y); 71 | return *this; 72 | } 73 | }; 74 | ``` 75 | 76 | ### Правило тройки (пятерки) 77 | 78 | Если явно объявить один из следующих методов: 79 | 80 | - деструктор 81 | - конструктор копирования 82 | - оператор копирования 83 | 84 | (после С++11, еще два) 85 | 86 | - конструктор перемещения 87 | - оператор перемещения 88 | 89 | То компилятор не будет генерировать остальные автоматически, поэтому если они вам нужны, вы должны реализовать их самостоятельно. 90 | 91 | ### rvalue-ссылка и lvalue-ссылка 92 | 93 | До стандарта С++11 было два типа значений: 94 | 95 | 1. lvalue 96 | 2. rvalue 97 | 98 | > "Объект - это некоторая **именованная область памяти**; lvalue - это выражение, обозначающее объект. Термин "lvalue" произошел от записи присваивания Е1 = Е2, в которой левый (left - левый(англ.), отсюда буква l, value - значение) операнд Е1 должен быть выражением lvalue." 99 | 100 | *Керниган и Ритчи* 101 | 102 | ```c++ 103 | int a = 1; 104 | int b = 2; 105 | int c = (a + b); 106 | int foo() { return 3; } 107 | int d = foo(); 108 | 109 | 1 = a; // left operand must be l-value 110 | foo() = 2; // left operand must be l-value 111 | (a + b) = 3; // left operand must be l-value 112 | ``` 113 | 114 | 1. Ссылается на объект - lvalue 115 | 2. Если можно взять адрес - lvalue 116 | 3. Все что не lvalue, то rvalue 117 | 118 | ```c++ 119 | int a = 3; 120 | a; // lvalue 121 | int& b = a; 122 | b; // lvalue, ссылается на a 123 | int* c = &a; 124 | *c; // lvalue, ссылается на a 125 | void foo(int val) 126 | { 127 | val; // lvalue 128 | } 129 | void foo(int& val) 130 | { 131 | val; // lvalue, ссылается на val 132 | } 133 | int& bar() { return a; } 134 | bar(); // lvalue, ссылается на a 135 | ``` 136 | 137 | ```c++ 138 | 3; // rvalue 139 | (a + b); // rvalue 140 | int bar() { return 1; } 141 | bar(); // rvalue 142 | ``` 143 | 144 | ##### lvalue-ссылка 145 | 146 | Ссылка на lvalue. 147 | 148 | ```c++ 149 | int a = 3; 150 | int& b = a; 151 | ``` 152 | 153 | ```c++ 154 | int& a = 3; // ошибка 155 | const int& a = 3; // ок 156 | a; // const lvalue 157 | ``` 158 | 159 | Объект жив до тех пор, пока жива ссылающаяся на него константная ссылка. 160 | 161 | ##### rvalue-ссылка 162 | 163 | ```c++ 164 | #include 165 | 166 | int x = 0; 167 | 168 | int val() { return 0; } 169 | int& ref() { return x; } 170 | 171 | void test(int&) 172 | { 173 | std::cout << "lvalue\n"; 174 | } 175 | 176 | void test(int&&) 177 | { 178 | std::cout << "rvalue\n"; 179 | } 180 | 181 | int main() 182 | { 183 | test(0); // rvalue 184 | test(x); // lvalue 185 | test(val()); // rvalue 186 | test(ref()); // lvalue 187 | test(std::move(x)); // rvalue 188 | return 0; 189 | } 190 | ``` 191 | 192 | > ```std::move``` приводит lvalue к rvalue 193 | 194 | ### Копирование 195 | 196 | Семантика: в результате копирования должна появится точная копия объекта. 197 | 198 | ```c++ 199 | int x = 3; 200 | int y = x; 201 | // x == y 202 | 203 | String a; 204 | String b = a; 205 | String c; 206 | c = a; 207 | // a == b == c 208 | ``` 209 | 210 | ```c++ 211 | class String 212 | { 213 | size_t size_; 214 | char* data_; 215 | public: 216 | ~String() 217 | { 218 | delete[] data_; 219 | } 220 | 221 | // String b1; 222 | // String b2 = b1; 223 | String(const String& copied) 224 | : data_(new char[copied.size_]) 225 | , size_(copied.size_) 226 | { 227 | std::copy(copied.data_, copied.data_ + size_, data_); 228 | } 229 | 230 | // String b1; 231 | // String b2; 232 | // b2 = b1; 233 | String& operator=(const String& copied) 234 | { 235 | // Плохо 236 | delete[] data_; 237 | data_ = new char[copied.size_]; 238 | size_ = copied.size_; 239 | std::copy(copied.data_, copied.data_ + size_, data_); 240 | return *this; 241 | } 242 | }; 243 | ``` 244 | 245 | ```c++ 246 | String b1; 247 | b1 = b1; 248 | 249 | std::vector words; 250 | ... 251 | words[to] = words[from]; 252 | ``` 253 | 254 | Проверяйте на присваивание самому себе. 255 | 256 | ```c++ 257 | String& operator=(const String& copied) 258 | { 259 | if (this == &copied) 260 | return *this; 261 | // Плохо 262 | delete[] data_; 263 | data_ = new char[copied.size_]; 264 | size_ = copied.size_; 265 | std::copy(copied.data_, copied.data_ + size_, data_); 266 | return *this; 267 | } 268 | ``` 269 | 270 | Финальный вариант: 271 | 272 | ```c++ 273 | String& operator=(const String& copied) 274 | { 275 | if (this == &copied) 276 | return *this; 277 | char* ptr = new char[copied.size_]; 278 | delete[] data_; 279 | data_ = ptr; 280 | size_ = copied.size_; 281 | std::copy(copied.data_, copied.data_ + size_, data_); 282 | return *this; 283 | } 284 | ``` 285 | 286 | > Подумайте, а стоит ли писать конструктор/оператор копирования самостоятельно? 287 | 288 | ##### Копирование и наследование 289 | 290 | ```c++ 291 | struct A 292 | { 293 | A() {} 294 | A(const A& a) {} 295 | virtual A& operator=(const A& copied) 296 | { return *this; } 297 | }; 298 | 299 | class B 300 | : public A 301 | { 302 | public: 303 | B() {} 304 | 305 | B(B& b) 306 | : A(b) 307 | { 308 | } 309 | 310 | A& operator=(const A& copied) override 311 | { 312 | A::operator=(copied); 313 | return *this; 314 | } 315 | }; 316 | ``` 317 | 318 | ##### Срезка 319 | 320 | ```c++ 321 | void foo(A a) 322 | { 323 | // Срезанный до А объект 324 | } 325 | 326 | B a; 327 | foo(a); 328 | ``` 329 | 330 | ##### Нежелательное копирование 331 | 332 | ```c++ 333 | void send(std::vector data) 334 | { 335 | ... 336 | } 337 | ``` 338 | 339 | ```c++ 340 | void print(const std::vector& data) 341 | ``` 342 | 343 | Используйте передачу по ссылке! 344 | 345 | ##### Явный запрет копирования 346 | 347 | До С++11: 348 | 349 | ```c++ 350 | class Noncopyable 351 | { 352 | Noncopyable(const Noncopyable&); 353 | Noncopyable& operator=(const Noncopyable&); 354 | }; 355 | 356 | class Buffer 357 | : private Noncopyable 358 | { 359 | }; 360 | ``` 361 | 362 | `boost::noncopyable` устроен именно так. 363 | 364 | С++11: 365 | 366 | ```c++ 367 | class Buffer 368 | { 369 | Buffer(const Buffer&) = delete; 370 | Buffer& operator=(const Buffer&) = delete; 371 | }; 372 | ``` 373 | 374 | ##### Явное указание компилятору сгенерировать конструктор и оператор копирования 375 | 376 | ```c++ 377 | class Buffer 378 | { 379 | public: 380 | Buffer(const Buffer&) = default; 381 | Buffer& operator=(const Buffer&) = default; 382 | }; 383 | ``` 384 | 385 | ### Перемещение 386 | 387 | Семантика: в результате перемещения в объекте, куда происходит перемещение, должна появиться точная копия перемещаемого объекта, оригинальный объект после этого остается в неопределенном, но корректном состоянии. 388 | 389 | ##### Передача владения 390 | 391 | ```c++ 392 | class unique_ptr 393 | { 394 | T* data_; 395 | }; 396 | ``` 397 | 398 | ##### Производительность 399 | 400 | ```c++ 401 | class Buffer 402 | { 403 | char* data_; 404 | size_t size_; 405 | }; 406 | ``` 407 | 408 | ### lvalue и rvalue начиная с C++11 409 | 410 | ![](images/values.png) 411 | 412 | ##### glvalue ("generalized" lvalue) 413 | 414 | Выражение, чьё вычисление определяет сущность объекта. 415 | 416 | ##### prvalue ("pure" rvalue) 417 | 418 | Выражение, чьё вычисление инициализирует объект или вычисляет значение операнда оператора, с соответствии с контекстом использования. 419 | 420 | ##### xvalue ("eXpiring" value) 421 | 422 | Это glvalue, которое обозначает объект, чьи ресурсы могут быть повторно использованы (обычно потому, что они находятся около конца своего времени жизни). 423 | 424 | ##### lvalue 425 | 426 | Это glvalue, которое не является xvalue. 427 | 428 | ##### rvalue 429 | 430 | Это prvalue или xvalue. 431 | 432 | #### Пример 433 | 434 | ##### lvalue 435 | 436 | Выражение является lvalue, если ссылается на объект уже имеющий имя доступное вне выражения. 437 | 438 | ```c++ 439 | int a = 3; 440 | a; // lvalue 441 | int& b = a; 442 | b; // lvalue 443 | int* c = &a; 444 | *c; // lvalue 445 | 446 | int& foo() { return a; } 447 | foo(); // lvalue 448 | ``` 449 | 450 | ##### xvalue 451 | 452 | 1. Результат вызова функции возвращающей rvalue-ссылку 453 | 454 | ```c++ 455 | int&& foo() { return 3; } 456 | foo(); // xvalue 457 | ``` 458 | 459 | 2. Явное приведение к rvalue 460 | 461 | ```c++ 462 | static_cast(5); // xvalue 463 | std::move(5); // эквивалентно static_cast 464 | ``` 465 | 466 | 3. Результат доступа к нестатическому члену, объекта xvalue значения 467 | 468 | ```c++ 469 | struct A 470 | { 471 | int i; 472 | }; 473 | 474 | A&& foo() { return A(); } 475 | 476 | foo().i; // xvalue 477 | ``` 478 | 479 | ##### prvalue 480 | 481 | Не принадлежит ни к lvalue, ни к xvalue. 482 | 483 | ```c++ 484 | int foo() { return 3; } 485 | foo(); // prvalue 486 | ``` 487 | 488 | ##### rvalue 489 | 490 | Все что принадлежит к xvalue или prvalue. 491 | 492 | ##### glvalue 493 | 494 | Все что принадлежит к xvalue или lrvalue. 495 | 496 | #### Практическое правило (Скотт Мейерс) 497 | 498 | 1. Можно взять адрес - lvalue 499 | 2. Ссылается на lvalue (T&, const T&) - lvalue 500 | 3. Иначе rvalue 501 | 502 | > Как правило rvalue соответствует временным объектам, например, возвращаемым из функций или создаваемых в результате неявных приведений типов. Также это большинство литералов. 503 | 504 | #### Классификация 505 | 506 | Есть имя | Может быть перемещено | Тип 507 | ---|---|--- 508 | да | нет | glvalue, lvalue 509 | да | да | rvalue, xvalue, glvalue 510 | нет | да | rvalue, prvalue 511 | 512 | ##### Еще примеры 513 | 514 | ```c++ 515 | void foo(int) {} 516 | void foo(int&) {} 517 | void foo(int&&) {} 518 | ``` 519 | 520 | ```c++ 521 | void foo(int) {} // <-- этот? 522 | void foo(int&) {} // // <-- или этот? 523 | void foo(int&&) {} 524 | 525 | int x = 1; 526 | foo(x); // lvalue 527 | ``` 528 | 529 | ```c++ 530 | int x = 1; 531 | int& y = x; 532 | foo(y); // lvalue 533 | 534 | void foo(int) {} // <-- этот? 535 | void foo(int&) {} // <-- или этот? 536 | void foo(int&&) {} 537 | ``` 538 | ```c++ 539 | foo(1); // rvalue 540 | 541 | void foo(int) {} // <-- этот? 542 | void foo(int&) {} 543 | void foo(int&&) {} // <-- или этот? 544 | ``` 545 | ```c++ 546 | int bar() { return 1; } 547 | foo(bar()); // rvalue 548 | 549 | void foo(int) {} // <-- этот? 550 | void foo(int&) {} 551 | void foo(int&&) {} // <-- или этот? 552 | ``` 553 | 554 | ```c++ 555 | foo(1 + 2); // rvalue 556 | 557 | void foo(int) {} // <-- этот? 558 | void foo(int&) {} 559 | void foo(int&&) {} // <-- или этот? 560 | ``` 561 | 562 | #### Конструктор/оператор перемещения 563 | 564 | ```c++ 565 | class Buffer 566 | { 567 | size_t size_; 568 | char* data_; 569 | public: 570 | ~Buffer() 571 | { 572 | delete[] data_; 573 | } 574 | 575 | // Buffer b1; 576 | // Buffer b2 = std::move(b1); 577 | Buffer(Buffer&& moved) 578 | : data_(moved.data_) 579 | , size_(moved.size_) 580 | { 581 | moved.data_ = nullptr; 582 | moved.size_ = 0; 583 | } 584 | 585 | // Buffer b1; 586 | // Buffer b2; 587 | // b2 = std::move(b1); 588 | Buffer& operator=(Buffer&& moved) 589 | { 590 | if (this == &moved) 591 | return *this; 592 | delete[] data_; 593 | data_ = moved.data_; 594 | size_ = moved.size_; 595 | moved.data_ = nullptr; 596 | moved.size_ = 0; 597 | return *this; 598 | } 599 | }; 600 | ``` 601 | 602 | ##### Явное указание компилятору сгенерировать конструктор и оператор перемещения 603 | 604 | ```c++ 605 | class Buffer 606 | { 607 | public: 608 | Buffer(Buffer&&) = default; 609 | Buffer& operator=(Buffer&&) = default; 610 | }; 611 | ``` 612 | 613 | ##### Явное указание компилятору запретить перемещение 614 | 615 | ```c++ 616 | class Buffer 617 | { 618 | public: 619 | Buffer(Buffer&&) = delete; 620 | Buffer& operator=(Buffer&&) = delete; 621 | }; 622 | ``` 623 | 624 | ##### Perfect forwarding 625 | 626 | Задача: передать аргумент не создавая временных копий и не изменяя типа передаваемого аргумента. 627 | 628 | ```c++ 629 | void bar(T&) {} 630 | void bar(T&&) {} 631 | ``` 632 | 633 | ```c++ 634 | template 635 | void foo(T x) 636 | { 637 | // копия 638 | bar(x); 639 | } 640 | ``` 641 | 642 | ```c++ 643 | template 644 | void foo(T& x) 645 | { 646 | // Может приводить к ошибкам 647 | // компиляции, если x - rvalue: 648 | // foo(T()); 649 | bar(x); 650 | } 651 | 652 | template 653 | void foo(T&& x) 654 | { 655 | // ок, но пришлось написать перегрузку 656 | bar(std::move(x)); 657 | } 658 | ``` 659 | 660 | Решение: 661 | 662 | ```c++ 663 | template 664 | void foo(T&& x) 665 | { 666 | bar(std::forward(x)); 667 | } 668 | ``` 669 | 670 | ##### Return value optimization (RVO) 671 | 672 | Позволяет сконструировать возвращаемый объект в точке вызова. 673 | 674 | ```c++ 675 | Server makeServer(uint16_t port) 676 | { 677 | Server server(port); 678 | server.setup(...); 679 | return server; 680 | } 681 | 682 | Server s = makeServer(8080); 683 | ``` 684 | 685 | Не мешайте компилятору: 686 | 687 | ```c++ 688 | Server&& makeServer(uint16_t port) 689 | { 690 | Server server(port); 691 | server.setup(...); 692 | return std::move(server); // так не надо 693 | } 694 | ``` 695 | 696 | ##### Copy elision 697 | 698 | Оптимизация компилятора разрешающая избегать лишнего вызова копирующего конструктора. 699 | 700 | ```c++ 701 | struct A 702 | { 703 | explicit A(int) {} 704 | A(const A&) {} 705 | }; 706 | 707 | A y = A(5); // Копирующий конструктор вызван не будет 708 | ``` 709 | 710 | > В копирующих конструкторах должна быть логика отвечающая только за копирование. 711 | 712 | ### Практическая часть 713 | 714 | Написать класс для работы с большими целыми числами. Размер числа ограничен только размером памяти. Нужно поддержать семантику работы с обычным int: 715 | 716 | ```c++ 717 | BigInt a = 1; 718 | BigInt b = a; 719 | BigInt c = a + b + 2; 720 | ``` 721 | 722 | Реализовать оператор вывода в поток, сложение, вычитание, унарный минус, все операции сравнения. 723 | 724 | std::vector и другие контейнеры использовать нельзя - управляйте памятью сами. 725 | 726 | EOF 727 | -------------------------------------------------------------------------------- /03.functions.md: -------------------------------------------------------------------------------- 1 | ### Пространства имен 2 | 3 | Проблема: 4 | 5 | ```c++ 6 | // math.h 7 | double cos(double x); 8 | ``` 9 | 10 | ```c++ 11 | // ваш код 12 | double cos(double x); 13 | ``` 14 | 15 | ##### Решение в стиле С: 16 | 17 | ```c++ 18 | // ваш код 19 | double fastCos(double x); 20 | ``` 21 | 22 | ##### Решение: 23 | 24 | ```c++ 25 | namespace fast 26 | { 27 | double cos(double x); 28 | } 29 | 30 | fast::cos(x); 31 | cos(x); // вызов из math.h 32 | ``` 33 | 34 | #### Поиск имен 35 | 36 | - Проверка в текущем namespace 37 | - Если имени нет и текущий namespace глобальный - ошибка 38 | - Рекурсивно поиск в родительском namespace 39 | 40 | ```c++ 41 | void foo() {} // ::foo 42 | 43 | namespace A 44 | { 45 | void foo() {} // A::foo 46 | 47 | namespace B 48 | { 49 | void bar() // A::B::foo 50 | { 51 | foo(); // A::foo 52 | ::foo(); // foo() 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | #### Ключевое слово using 59 | 60 | Добавляет имена из указанного namespace в текущий namespace. 61 | 62 | ```c++ 63 | void foo() 64 | { 65 | using namespace A; 66 | // видимо все из A 67 | } 68 | ``` 69 | 70 | ```c++ 71 | void foo() 72 | { 73 | using namespace A::foo; 74 | // видима только A::foo() 75 | } 76 | ``` 77 | 78 | ```c++ 79 | void foo() 80 | { 81 | namespace ab = A::B; 82 | ab::bar(); // A::B::bar() 83 | } 84 | ``` 85 | 86 | ##### using может приводить к проблемам 87 | 88 | ```c++ 89 | using namespace fast; 90 | 91 | cos(x); // ??? 92 | cos(x); // ??? 93 | ``` 94 | 95 | > Не используйте using namespace в заголовочных файлах! 96 | 97 | ### Функции 98 | 99 | ```c++ 100 | int rollDice() 101 | { 102 | return 4; 103 | } 104 | 105 | int square(int x) 106 | { 107 | int tmp = x * x; 108 | return tmp; 109 | } 110 | ``` 111 | 112 | ``` 113 | square(int): 114 | ; Пролог 115 | push ebp 116 | mov ebp, esp 117 | 118 | ; Выделение памяти 119 | sub esp, 16 120 | 121 | ; Тело 122 | mov eax, DWORD PTR [ebp+8] 123 | imul eax, eax 124 | mov DWORD PTR [ebp-4], eax 125 | 126 | ; Возврат результата 127 | mov eax, DWORD PTR [ebp-4] 128 | 129 | ; Эпилог 130 | mov esp, ebp 131 | pop ebp 132 | ret 133 | ``` 134 | 135 | #### Конвенции вызова x32 136 | 137 | ##### cdecl 138 | 139 | Исторически принятое соглашение для языка С. 140 | 141 | Аргументы функций передаются через стек, справа налево. Аргументы, размер которых меньше 4-х байт, расширяются до 4-х байт. Очистку стека производит вызывающая программа. integer-like результат возвращается через регистр EAX. 142 | 143 | Перед вызовом функции вставляется код, называемый прологом (prolog) и выполняющий следующие действия: 144 | - сохранение значений регистров, используемых внутри функции 145 | - запись в стек аргументов функции 146 | 147 | После вызова функции вставляется код, называемый эпилогом (epilog) и выполняющий следующие действия: 148 | - восстановление значений регистров, сохранённых кодом пролога 149 | - очистка стека (от локальных переменных функции) 150 | 151 | ##### thiscall 152 | Соглашение о вызовах, используемое компиляторами для языка C++ при вызове методов классов. 153 | 154 | Отличается от **cdecl** соглашения только тем, что указатель на объект, для которого вызывается метод (указатель this), записывается в регистр ecx. 155 | 156 | ##### fastcall 157 | 158 | Передача параметров через регистры: если для сохранения всех параметров и промежуточных результатов регистров не достаточно, используется стек (в gcc через регистры ecx и edx передаются первые 2 параметра). 159 | 160 | #### Смотрим сгенерированный код 161 | 162 | ```c++ 163 | [[gnu::fastcall]] 164 | void foo1(int x, int y, int z, int a) 165 | { 166 | } 167 | 168 | void foo2(int x, int y, int z, int a) 169 | { 170 | } 171 | 172 | void bar1() 173 | { 174 | foo1(1, 2, 3, 4); 175 | } 176 | 177 | void bar2() 178 | { 179 | foo2(5, 6, 7, 8); 180 | } 181 | ``` 182 | 183 | ``` 184 | g++ -c test.cpp -o test.o -O0 -m32 185 | ``` 186 | 187 | ``` 188 | objdump -d test.o 189 | ``` 190 | 191 | ```nasm 192 | 000005c8 <_Z4bar1v>: 193 | 5c8: 6a 04 push $0x4 194 | 5ca: 6a 03 push $0x3 195 | 5cc: ba 02 00 00 00 mov $0x2,%edx 196 | 5d1: b9 01 00 00 00 mov $0x1,%ecx 197 | 5d6: e8 b5 ff ff ff call 590 <_Z4foo1iiii> 198 | 5dd: c3 ret 199 | 200 | 000005eb <_Z4bar2v>: 201 | 5eb: 6a 08 push $0x8 202 | 5ed: 6a 07 push $0x7 203 | 5ef: 6a 06 push $0x6 204 | 5f1: 6a 05 push $0x5 205 | 5f3: e8 b3 ff ff ff call 5ab <_Z4foo2iiii> 206 | 5fd: c3 ret 207 | ``` 208 | 209 | ##### System V AMD64 ABI (Linux, MacOS, FreeBSD, Solaris) 210 | 211 | - 6 регистров (RDI, RSI, RDX, RCX, R8, R9) для передачи integer-like аргументов 212 | - 8 регистров (XMM0-XMM7) для передачи double/float 213 | - если аргументов больше, они передаются через стек 214 | - для возврата integer-like значений используются RAX и RDX (64 бит + 64 бит) 215 | 216 | 217 | ### Встраивание функций (inline) 218 | 219 | Иногда вызова функции не будет - оптимизирующий компилятор выполнит ее встраивание по месту вызова. 220 | 221 | Можно подсказать компилятору выполнить встраивание: 222 | 223 | ```c++ 224 | inline void foo() 225 | { 226 | } 227 | ``` 228 | 229 | Но, компилятор умный и скорее всего проигнорирует inline, но можно попросить настойчивей: 230 | 231 | ```c++ 232 | // ms vc 233 | __forceinline void foo() 234 | { 235 | } 236 | ``` 237 | ```c++ 238 | // gcc 239 | __attribute__((always_inline)) void foo() 240 | { 241 | } 242 | ``` 243 | 244 | Все равно нет гарантий. 245 | 246 | ##### Тот случай, когда макросы уместны 247 | 248 | ```c++ 249 | #ifdef __GNUC__ 250 | #define __forceinline __attribute__((always_inline)) 251 | #endif 252 | ``` 253 | 254 | ### Ссылки 255 | 256 | Ссылка - псевдоним объекта. 257 | 258 | Главное отличие от указателя - ссылка должна быть проинициализирована при объявлении и до конца своей жизни ссылается только на один объект. 259 | 260 | ```c++ 261 | int a = 1; 262 | int b = 2; 263 | int* ptr = nullptr; 264 | ptr = &a; 265 | ptr = &b; 266 | int& ref; // Ошибка 267 | int& ref = a; // ref ссылается на a 268 | ref = 5; // Теперь a == 5 269 | ref = b; // ref не стала указывать на b, 270 | // мы просто скопировали значение из b в a 271 | ref = 7; // a == 7, b == 2 272 | ``` 273 | ```c++ 274 | int a = 2; 275 | int* ptr = nullptr; 276 | int*& ptrRef = ptr; // ptrRef ссылается на ptr 277 | ptrRef = &a; // теперь ptr хранит адрес a 278 | ``` 279 | 280 | ### Передача аргументов в функции 281 | 282 | ##### По значению 283 | 284 | ```c++ 285 | void foo(int x) 286 | { 287 | x = 3; 288 | } 289 | 290 | int x = 1; 291 | foo(x); 292 | // x == 1 293 | 294 | void bar(BigObject o) { ... } 295 | ``` 296 | 297 | - В функции окажется копия объекта, ее изменение не отразится на оригинальном объекте 298 | - Копировать большие объекты может оказаться накладно 299 | 300 | ##### По ссылке 301 | 302 | ```c++ 303 | void foo(int& x) 304 | { 305 | x = 3; 306 | } 307 | 308 | int x = 1; 309 | foo(x); 310 | // x == 3 311 | 312 | void bar(BigObject& o) { ... } 313 | ``` 314 | 315 | - Копирования не происходит, все изменения объекта внутри функции отражаются на объекте 316 | - Следует использовать, если надо изменить объект внутри функции 317 | 318 | ```c++ 319 | void swap(int& x, int& y) 320 | { 321 | int tmp = x; 322 | x = y; 323 | y = tmp; 324 | } 325 | ``` 326 | 327 | ##### По константной ссылке 328 | 329 | ```c++ 330 | void foo(const int& x) 331 | { 332 | x = 3; // ошибка компиляции 333 | } 334 | 335 | void bar(const BigObject& o) { ... } 336 | ``` 337 | - Копирования не происходит, при попытке изменения объекта будет ошибка 338 | - Большие объекты выгодней передавать по ссылке, маленькие - наоборот 339 | 340 | ##### По указателю 341 | 342 | ```c++ 343 | void foo(int* x) 344 | { 345 | *x = 3; 346 | } 347 | 348 | void bar(BigObject* o) { ... } 349 | 350 | void foo(const int* x) 351 | { 352 | *x = 3; // ошибка компиляции 353 | } 354 | 355 | void bar(const BigObject* o) { ... } 356 | ``` 357 | 358 | - Копирования не происходит 359 | - Если указатель на константный объект, то при попытке изменения объекта будет ошибка 360 | - Есть дополнительный уровень косвенности, возможно придется что-то подгрузить в кеш из дальнего участка памяти 361 | - Реализуется optional-концепция 362 | 363 | ```c++ 364 | int countObject(time_t* fromDate, time_t* toDate) 365 | { 366 | const auto begin = 367 | fromDate == nullptr 368 | ? objects_.begin() 369 | : objects_.findFirst(fromDate); 370 | } 371 | ``` 372 | 373 | ##### По универсальной ссылке 374 | 375 | ```c++ 376 | void foo(int&& x) { ... } 377 | void bar(BigObject&& o) { ... } 378 | ``` 379 | Поговорим в отдельной лекции. 380 | 381 | ##### Перегрузка функций 382 | 383 | ```c++ 384 | void print(int x) // 1 385 | { 386 | std::cout << x << std::endl; 387 | } 388 | 389 | void print(bool x) // 2 390 | { 391 | std::cout << (x ? "true" : "false") << std::endl; 392 | } 393 | 394 | print(10); // 1 395 | print(true); // 2 396 | ``` 397 | 398 | ##### Опасность перегрузки 399 | 400 | ```c++ 401 | void print(const std::string& x) // 3 402 | { 403 | std::cout << "string" << std::endl; 404 | } 405 | 406 | print("hello!"); // 2 const char* приводится к bool 407 | ``` 408 | 409 | > Перегруженная функция - декорированная функция 410 | 411 | 412 | #### Указатель на функцию 413 | 414 | ```c++ 415 | void foo(int x) 416 | { 417 | } 418 | 419 | typedef void (*FooPtr)(int); 420 | 421 | FooPtr ptr = foo; 422 | ptr(5); 423 | ``` 424 | 425 | ```c++ 426 | using FooPtr = void(*)(int); 427 | ``` 428 | 429 | ##### Функции высшего порядка 430 | 431 | Функция высшего порядка — функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата. 432 | 433 | #### Сценарии использования указателей на функции 434 | 435 | ##### Настройка поведения 436 | 437 | ```c++ 438 | void sort(int* data, size_t size, bool (*compare)(int x, int y)); 439 | 440 | bool less(int x, int y) 441 | { 442 | return x < y; 443 | } 444 | 445 | sort(data, 100, less); 446 | ``` 447 | 448 | ##### Фунции обратного вызова (callback) 449 | 450 | ```c++ 451 | using OnMailReceived = void (*)(const Mail& newMail); 452 | 453 | void addOnMailReceivedObserver(OnMailReceived handler); 454 | 455 | void onMailReceivedHandler(const Mail& newMail) 456 | { 457 | ... 458 | } 459 | 460 | addOnMailReceivedObserver(onMailReceivedHandler); 461 | ``` 462 | 463 | ##### Конвееры 464 | 465 | ```c++ 466 | using MoveFunctionPtr = void (*)(int& x, int& y); 467 | 468 | void moveLeft(int& x, int& y) { ... } 469 | void moveRight(int& x, int& y) { ... } 470 | 471 | std::vector trajectory = 472 | { 473 | moveLeft, 474 | moveLeft, 475 | moveRight, 476 | }; 477 | 478 | int x = 0; 479 | int y = 0; 480 | for (auto& func : trajectory) 481 | { 482 | func(x, y); 483 | } 484 | ``` 485 | 486 | ### Лямбда-функции 487 | 488 | ```c++ 489 | auto lessThen3 = [](int x) { return x < 3; }; 490 | 491 | if (lessThen3(x)) { ... } 492 | ``` 493 | 494 | ##### Синтаксис 495 | 496 | ```c++ 497 | [список_захвата](список_параметров) { тело_функции } 498 | ``` 499 | ```c++ 500 | [список_захвата](список_параметров) -> тип_возвращаемого_значения 501 | { тело_функции } 502 | ``` 503 | 504 | #### Захват переменных 505 | 506 | ```c++ 507 | int x = 5; 508 | int y = 7; 509 | auto foo = [x, &y]() { y = 2 * x }; 510 | foo(); 511 | ``` 512 | 513 | Если не указать &, то будет захват по значению, то есть копирование объекта; если указать, то по ссылке (нет копирования, можификации внутри функции отразяться на оригинальном объекте). 514 | 515 | ```c++ 516 | // Захват всех переменных в области видимости по значению 517 | auto foo = [=]() {}; 518 | ``` 519 | 520 | ```c++ 521 | // Захват всех переменных в области видимости по ссылке 522 | auto foo = [&]() {}; 523 | ``` 524 | 525 | > Использование переменных, определённых в той же области видимости, что и лямбда-функция, называют замыканием. 526 | 527 | ##### Примеры захвата 528 | 529 | ```c++ 530 | [] // без захвата переменных из внешней области видимости 531 | [=] // все переменные захватываются по значению 532 | [&] // все переменные захватываются по ссылке 533 | [x, y] // захват x и y по значению 534 | [&x, &y] // захват x и y по ссылке 535 | [in, &out] // захват in по значению, а out — по ссылке 536 | [=, &out1, &out2] // захват всех переменных по значению, 537 | // кроме out1 и out2, которые захватываются по ссылке 538 | [&, x, &y] // захват всех переменных по ссылке, кроме x, 539 | // которая захватывается по значению 540 | ``` 541 | 542 | ##### mutable 543 | 544 | ```c++ 545 | int x = 3; 546 | auto foo = [x]() mutable 547 | { 548 | x += 3; 549 | ... 550 | } 551 | ``` 552 | 553 | #### std::function 554 | 555 | ```c++ 556 | #include 557 | 558 | using MoveFunction = 559 | std::function; 560 | 561 | MoveFunction getRandomDirection() { ... } 562 | 563 | std::vector trajectory = 564 | { 565 | moveLeft, 566 | moveLeft, 567 | moveRight, 568 | [](int& x, int& y) 569 | { 570 | ... 571 | }, 572 | moveLeft, 573 | getRandomDirection() 574 | }; 575 | 576 | int x = 0; 577 | int y = 0; 578 | for (auto& func : trajectory) 579 | { 580 | func(x, y); 581 | } 582 | ``` 583 | 584 | ### Структуры и классы 585 | 586 | Информация о пользователе: 587 | 588 | 1. Имя 589 | 2. email 590 | 591 | ```c++ 592 | std::string name; 593 | std::string email; 594 | ``` 595 | 596 | ##### Агрегируем данные 597 | 598 | Для упрощения программы, логически связанные данные можно объединить. 599 | 600 | ```c++ 601 | struct User 602 | { 603 | std::string name; 604 | std::string email; 605 | }; 606 | 607 | const User user = 608 | { "Bob", "bob@mail.ru" }; 609 | 610 | std::cout << user.name; 611 | ``` 612 | 613 | > name, email - поля структуры 614 | 615 | ##### Много пользователей (array of structs) 616 | 617 | ```c++ 618 | User users[N]; 619 | ``` 620 | 621 | ##### Много пользователей (struct of arrays) 622 | 623 | ```c++ 624 | struct Users 625 | { 626 | std::string name[N]; 627 | std::string email[N]; 628 | }; 629 | ``` 630 | 631 | ### Модификаторы доступа 632 | 633 | ```c++ 634 | struct A 635 | { 636 | public: 637 | int x; // Доступно всем 638 | protected: 639 | int y; // Наследникам и объектам класса 640 | private: 641 | int z; // Только объектам класса 642 | }; 643 | 644 | A a; 645 | a.x = 1; // ок 646 | a.y = 1; // ошибка 647 | a.z = 1; // ошибка 648 | ``` 649 | 650 | > Объект - сущность в адресном пространстве компьютера, появляющаяся 651 | при создании класса. 652 | 653 | ### struct vs class 654 | 655 | В С++ struct от class отличаются только модификатором доступа по умолчанию. По умолчанию содержимое struct доступно извне (public), а содержимое class - нет (private). 656 | 657 | ```c++ 658 | class A 659 | { 660 | int x; // private 661 | }; 662 | 663 | struct B 664 | { 665 | int x; // public 666 | } 667 | ``` 668 | 669 | #### Методы класса 670 | 671 | ```c++ 672 | struct User 673 | { 674 | void serialize(Stream& out) 675 | { 676 | out.write(name); 677 | out.write(email); 678 | } 679 | 680 | private: 681 | std::string name; 682 | std::string email; 683 | }; 684 | ``` 685 | 686 | > serialize - метод класса 687 | 688 | > Методы класса, доступные для использования другими классами, представляют его интерфейс 689 | 690 | #### Классы в С 691 | 692 | ```c 693 | struct File 694 | { 695 | int descriptor; 696 | char buffer[BufferSize]; 697 | }; 698 | 699 | File* openFile(const char* fileName) 700 | { 701 | File* file = (File*) malloc(sizeof(File)); 702 | file->descriptor = open(fileName, O_CREAT); 703 | return file; 704 | } 705 | 706 | void write(File* file, const char* data, size_t size) 707 | { 708 | ... 709 | } 710 | 711 | void close(File* file) 712 | { 713 | close(file->descriptor); 714 | free(file); 715 | } 716 | 717 | File* file = openFile("some_file.dat"); 718 | write(file, data, size); 719 | close(file); 720 | ``` 721 | 722 | ```c++ 723 | class File 724 | { 725 | public: 726 | File(const char* fileName) 727 | { 728 | descriptor = open(fileName, O_CREAT); 729 | } 730 | 731 | void write(const char* data, size_t size) 732 | { 733 | ... 734 | } 735 | 736 | ~File() 737 | { 738 | close(descriptor); 739 | } 740 | 741 | private: 742 | int descriptor; 743 | char buffer[BufferSize]; 744 | }; 745 | 746 | File file("some_file.dat"); 747 | file.write(data, size); 748 | ``` 749 | 750 | #### Декорирование методов класса 751 | 752 | ```c++ 753 | struct A 754 | { 755 | void foo(); // _ZN1A3fooEv 756 | }; 757 | 758 | void bar(); // _Z3barv 759 | ``` 760 | 761 | ### Указатель на экземпляр класса 762 | 763 | ```c++ 764 | void write([File* this], const char* data, size_t size) 765 | { 766 | this->descriptor ... 767 | } 768 | ``` 769 | 770 | > Метод класса - обычная функция, которая неявно получает указатель на объект класса (this) 771 | 772 | ```c++ 773 | struct A 774 | { 775 | void foo() { std::cout << "ok"; } 776 | void bar() { std::cout << x; } 777 | 778 | int x; 779 | }; 780 | 781 | A* a = nullptr; 782 | a->foo(); // Ок 783 | a->bar(); // Разыменование нулевого указателя 784 | ``` 785 | 786 | ``` 787 | void foo([A* this]) 788 | { 789 | std::cout << "ok"; 790 | } 791 | 792 | void bar([A* this]) 793 | { 794 | std::cout << [this->]x; 795 | } 796 | ``` 797 | 798 | ### Домашнее задание 799 | 800 | Написать библиотеку-парсер строк состоящих из следующих токенов: 801 | 802 | - строка 803 | - число 804 | 805 | Число состоит из символов от 0 до 9, строка - все остальные символы. Токены разделены пробелами, символами табуляции и первода строки. 806 | 807 | Пользователь библиотеки должен иметь возможность зарегистрировать колбек-функцию вызываемую каждый раз, когда найден токен, функция получает токен. Должно быть возможно зарегистрировать свой обработчик под каждый тип токена. Также должны быть колбеки вызываемые перед началом парсинга и по его окончанию. 808 | 809 | EOF 810 | -------------------------------------------------------------------------------- /05.templates.md: -------------------------------------------------------------------------------- 1 | ### Шаблоны 2 | 3 | ##### Шаблоны классов 4 | 5 | ```c++ 6 | class Matrix 7 | { 8 | double* data_; 9 | }; 10 | ``` 11 | 12 | ```c++ 13 | class MatrixDouble 14 | { 15 | double* data_; 16 | }; 17 | 18 | class MatrixInt 19 | { 20 | int* data_; 21 | }; 22 | ``` 23 | 24 | ```c++ 25 | template 26 | class Matrix 27 | { 28 | T* data_; 29 | }; 30 | ``` 31 | 32 | ```c++ 33 | Matrix m; 34 | Matrix m; 35 | ``` 36 | 37 | ##### Шаблоны функций 38 | 39 | ```c++ 40 | template 41 | void printLine(const T& value) 42 | { 43 | std::cout << value << '\n'; 44 | } 45 | ``` 46 | ```c++ 47 | printLine(5); 48 | ``` 49 | 50 | Компилятор может самостоятельно вывести тип шаблона в зависимости от аргументов вызова. 51 | 52 | ```c++ 53 | printLine(5); 54 | ``` 55 | 56 | ##### class или typename 57 | 58 | ```c++ 59 | template 60 | void printLine(const T& value) 61 | { 62 | } 63 | ``` 64 | ```c++ 65 | template 66 | void printLine(const T& value) 67 | { 68 | } 69 | ``` 70 | 71 | Никакой разницы. 72 | 73 | #### Инстанцирование шаблона 74 | 75 | Инстанцирование шаблона – это генерация кода функции или класса по шаблону для конкретных параметров. 76 | 77 | ```c++ 78 | template 79 | bool lessThan7(T value) { return value < 7; } 80 | ``` 81 | 82 | ```c++ 83 | lessThan7(5); // Инстанцирование 84 | // bool print(int value) { return value < 7; } 85 | 86 | lessThan7(5.0); // Инстанцирование 87 | // bool print(double value) { return value < 7; } 88 | ``` 89 | 90 | ##### Явное указание типа 91 | 92 | ```c++ 93 | lessThan7(5); // Инстанцирование 94 | // bool print(double value) { return value < 7; } 95 | ``` 96 | 97 | #### Константы как аргументы шаблона 98 | 99 | ```c++ 100 | template 101 | class Array 102 | { 103 | T data_[Size]; 104 | }; 105 | ``` 106 | 107 | ```c++ 108 | Array a; 109 | ``` 110 | 111 | #### Ограничения на параметры шаблона не являющиеся типами 112 | 113 | Так можно: 114 | 115 | ```c++ 116 | template 117 | int foo() 118 | { 119 | return N * 2; 120 | } 121 | ``` 122 | 123 | А double нельзя: 124 | 125 | ```c++ 126 | template // Ошибка 127 | void foo() 128 | { 129 | } 130 | ``` 131 | 132 | float тоже нельзя. 133 | 134 | > Причины исторические, почему не исправлено до сих пор не знаю. 135 | 136 | ###### Параметры шаблона должны быть известны на этапе компиляции. 137 | 138 | ```c++ 139 | template 140 | void foo() { } 141 | 142 | int x = 3; 143 | foo(); // Ошибка 144 | ``` 145 | 146 | Константы на литералы можно: 147 | 148 | ```c++ 149 | template 150 | void foo() {} 151 | 152 | const int x = 3; 153 | foo(); // Ok 154 | ``` 155 | 156 | А с обычной константой нельзя: 157 | 158 | ```c++ 159 | int bar() { return 0; } 160 | 161 | template 162 | void foo() { } 163 | 164 | const int x = bar(); 165 | foo(); // Ошибка 166 | ``` 167 | 168 | Но если вычислять значение во время компиляции, то можно: 169 | 170 | ```c++ 171 | constexpr int bar() { return 0; } 172 | 173 | template 174 | void foo() {} 175 | 176 | const int x = bar(); 177 | foo(); // Ok 178 | ``` 179 | 180 | > constexpr говорит компилятору, что надо стараться вычислить значение на этапе компиляции 181 | 182 | Нельзя использовать объекты класса: 183 | 184 | ```c++ 185 | struct A {}; 186 | 187 | template // Ошибка 188 | void foo() 189 | { 190 | } 191 | ``` 192 | 193 | Можно указатель на const char: 194 | 195 | ```c++ 196 | template 197 | void foo() 198 | { 199 | } 200 | ``` 201 | 202 | И это даже можно инстанцировать nullptr или 0: 203 | 204 | ```c++ 205 | foo(); 206 | foo<0>(); 207 | ``` 208 | 209 | Но нельзя литералом: 210 | 211 | ```c++ 212 | foo<"some text">(); // Ошибка 213 | ``` 214 | 215 | #### Параметры шаблона по умолчанию 216 | 217 | ```c++ 218 | template 219 | void foo() 220 | { 221 | } 222 | 223 | foo(); 224 | ``` 225 | 226 | ```c++ 227 | template > 228 | class Queue 229 | { 230 | ContainerT data_; 231 | }; 232 | 233 | Queue queue; 234 | ``` 235 | 236 | ### Специализация шаблона 237 | 238 | ```c++ 239 | template 240 | class Vector 241 | { 242 | ... 243 | } 244 | 245 | template <> 246 | class Vector 247 | { 248 | ... 249 | }; 250 | ``` 251 | 252 | Суммирование последовательности от n, до 0: 253 | 254 | ```c++ 255 | #include 256 | 257 | template 258 | int sum(); 259 | 260 | template <> 261 | int sum<0>() { return 0; } 262 | 263 | // template <> 264 | // int sum<1>() { return 1; } 265 | // template <> 266 | // int sum<2>() { return 2 + 1; } 267 | // ... 268 | 269 | template 270 | int sum() 271 | { 272 | return n + sum(); 273 | } 274 | 275 | int main() 276 | { 277 | std::cout << sum<3>() << '\n'; 278 | return 0; 279 | } 280 | ``` 281 | 282 | ```nasm 283 | int sum<0>(): 284 | mov eax, 0 285 | ret 286 | main: 287 | call int sum<3>() 288 | call operator<<(int) 289 | ret 290 | int sum<3>(): 291 | call int sum<2>() 292 | add eax, 3 293 | ret 294 | int sum<2>(): 295 | call int sum<1>() 296 | add eax, 2 297 | ret 298 | int sum<1>(): 299 | call int sum<0>() 300 | add eax, 1 301 | ret 302 | ``` 303 | 304 | #### Разбухание кода 305 | 306 | Необдуманное использование шаблонов может привести к разбуханию кода, кода становится много, он перестает помещаться в кеш, что ведет к существенным издержкам. 307 | 308 | ##### Еще реализация суммирования 309 | 310 | ```c++ 311 | template 312 | struct sum; 313 | 314 | template <> 315 | struct sum<0> 316 | { 317 | static constexpr int value = 0; 318 | }; 319 | 320 | template 321 | struct sum 322 | { 323 | static constexpr int value = n + sum::value; 324 | }; 325 | 326 | int main() 327 | { 328 | std::cout << sum<3>::value << '\n'; 329 | return 0; 330 | } 331 | ``` 332 | 333 | ```nasm 334 | main: 335 | mov esi, 6 336 | call operator<<(int) 337 | ret 338 | ``` 339 | 340 | ##### Вычисления времени компиляции 341 | 342 | А стоит ли? 343 | 344 | 1. Сложно для понимания и поддержки 345 | 2. Замедляет компиляцию 346 | 347 | > Вероятно лучшей альтернативой будет скрипт делающий вычисления и генерирующий С++ код из констант с рассчитаными значениями. 348 | 349 | ### Псевдонимы типов 350 | 351 | Старый способ: 352 | 353 | ```c++ 354 | typedef int Seconds; 355 | typedef Queue IntegerQueue; 356 | 357 | Seconds i = 5; 358 | IntegerQueue j; 359 | ``` 360 | 361 | Новый (рекомендуемый) способ: 362 | 363 | ```c++ 364 | using Seconds = int; 365 | using IntegerQueue = Queue; 366 | 367 | Seconds i = 5; 368 | IntegerQueue j; 369 | ``` 370 | 371 | ##### Псевдонимы типов для шаблонов: 372 | 373 | ```c++ 374 | template 375 | using MyQueue = Queue>; 376 | 377 | MyQueue y; 378 | ``` 379 | 380 | #### Новый синтаксис функций 381 | 382 | ```c++ 383 | auto foo() -> void 384 | { 385 | } 386 | ``` 387 | 388 | #### auto 389 | 390 | Позволяет статически определить тип по типу выражения. 391 | 392 | ```c++ 393 | auto i = 5; 394 | auto j = foo(); 395 | ``` 396 | 397 | #### range-based for и auto 398 | 399 | ```c++ 400 | for (auto i : { 1, 2, 3 }) 401 | std::cout << i; 402 | ``` 403 | 404 | ```c++ 405 | for (auto& i : data) 406 | i.foo(); 407 | ``` 408 | 409 | #### decltype 410 | 411 | Позволяет статически определить тип по типу выражения. 412 | 413 | ```c++ 414 | int foo() { return 0; } 415 | 416 | decltype(foo()) x = 5; 417 | // decltype(foo()) -> int 418 | // int x = 5; 419 | ``` 420 | 421 | ```c++ 422 | void foo(decltype(bar()) i) 423 | { 424 | } 425 | ``` 426 | 427 | #### Определение типа аргументов шаблона функций 428 | 429 | ```c++ 430 | template 431 | T min(T x, T y) 432 | { 433 | return x < y ? x : y; 434 | } 435 | 436 | min(1, 2); // ok 437 | min(0.5, 2); // error 438 | min(0.5, 2); // ok 439 | ``` 440 | ```c++ 441 | template 442 | X min(X x, Y y) 443 | { 444 | return x < y ? x : y; 445 | } 446 | 447 | min(1.5, 2); // ok 448 | min(1, 0.5); // ok? 449 | ``` 450 | ```c++ 451 | template 452 | auto min(X x, Y y) -> decltype(x + y) 453 | { 454 | return x < y ? x : y; 455 | } 456 | 457 | min(1.5, 2); // ok 458 | min(1, 0.5); // ok 459 | ``` 460 | 461 | #### typename 462 | 463 | ```c++ 464 | struct String 465 | { 466 | using Char = wchar_t; 467 | }; 468 | 469 | template 470 | class Parser 471 | { 472 | T::Char buffer[]; // Ошибка 473 | }; 474 | ``` 475 | 476 | Если компилятор встречая идентификатор в шаблоне, может его трактовать как тип или что-то иное (например, как статическую переменную), то он выбирает иное. 477 | 478 | ```c++ 479 | struct String 480 | { 481 | using Char = wchar_t; 482 | }; 483 | 484 | template 485 | class Parser 486 | { 487 | typename T::Char buffer[]; // Ok 488 | }; 489 | ``` 490 | ##### Ошибка инстанцирования шаблона 491 | 492 | ```c++ 493 | template 494 | bool lessThan7(T value) { return value < 7; } 495 | ``` 496 | 497 | ```c++ 498 | struct A {}; 499 | A a; 500 | lessThan7(a); // Инстанцирование 501 | // bool print(A value) { return value < 7; } 502 | // Ошибка инстанцирования, тип а не имеет operator<(int) 503 | ``` 504 | 505 | #### SFINAE (Substitution Failure Is Not An Error) 506 | 507 | При определении перегрузок функции ошибочные инстанциации шаблонов не вызывают ошибку компиляции, а отбрасываются из списка кандидатов на наиболее подходящую перегрузку. 508 | 509 | Неудачное инстанцирование шаблона - это не ошибка. 510 | 511 | Например, позволяет на этапе компиляции выбрать нужную функцию: 512 | 513 | ```c++ 514 | // C++11 515 | 516 | template 517 | void clear(T& t, 518 | typename std::enable_if::value>::type* = nullptr) 519 | { 520 | std::memset(&t, 0, sizeof(t)); 521 | } 522 | 523 | // Для не-POD типов 524 | template 525 | void clear(T& t, 526 | typename std::enable_if::value>::type* = nullptr) 527 | { 528 | t = T{}; 529 | } 530 | ``` 531 | 532 | ##### is_pod 533 | 534 | ```c++ 535 | template 536 | struct is_pod 537 | { 538 | static constexpr bool value = false; 539 | }; 540 | ``` 541 | 542 | ```c++ 543 | template <> 544 | struct is_pod 545 | { 546 | static constexpr bool value = true; 547 | }; 548 | ``` 549 | 550 | ##### enable_if 551 | 552 | ```c++ 553 | template 554 | struct enable_if 555 | { 556 | }; 557 | 558 | // Частичная специализация для true 559 | template 560 | struct enable_if 561 | { 562 | using type = T; 563 | }; 564 | 565 | enable_if::type // Ошибка, нет type 566 | enable_if::type // Ок, type == int 567 | ``` 568 | 569 | ```c++ 570 | // C++14 571 | 572 | template 573 | void clear(T& t, std::enable_if_t::value>* = nullptr) 574 | { 575 | std::memset(&t, 0, sizeof(t)); 576 | } 577 | 578 | // Для не-POD типов 579 | template 580 | void clear(T& t, std::enable_if_t::value>* = nullptr) 581 | { 582 | t = T{}; 583 | } 584 | ``` 585 | 586 | Можно получить на этапе компиляции информацию о типе, например, проверим есть ли у класса некий метод: 587 | 588 | ```c++ 589 | struct A 590 | { 591 | void foo() {} 592 | }; 593 | 594 | struct B 595 | { 596 | }; 597 | 598 | template 599 | struct HasFoo 600 | { 601 | static constexpr bool value = true; 602 | }; 603 | 604 | int main() 605 | { 606 | std::cout << hasFoo::value << '\n'; 607 | std::cout << hasFoo::value << '\n'; 608 | return 0; 609 | } 610 | ``` 611 | 612 | ```c++ 613 | template 614 | struct HasFoo 615 | { 616 | static constexpr bool value = ???; 617 | }; 618 | ``` 619 | 620 | Нам нужно будет 2 функции: одна принимает класс с нужным нам методом, другая принимает все остальное: 621 | 622 | ```c++ 623 | template 624 | struct HasFoo 625 | { 626 | // Принимает все 627 | static int check(...); 628 | 629 | // Принимает нужный нам класс, 630 | // где есть какая-то foo() 631 | template 632 | static auto check(U* u) -> decltype(u->foo()); 633 | }; 634 | ``` 635 | 636 | По возвращаемому функцией типу мы поймем, какая из перегрузок была использована, если тип совпадет, то это то, что нам нужно. 637 | 638 | Проверка совпадения типов: 639 | 640 | ```c++ 641 | template 642 | struct IsSame 643 | { 644 | static constexpr bool value = false; 645 | }; 646 | 647 | template 648 | struct IsSame 649 | { 650 | static constexpr bool value = true; 651 | }; 652 | ``` 653 | 654 | Финальный вариант: 655 | 656 | ```c++ 657 | template 658 | struct HasFoo 659 | { 660 | private: 661 | static int check(...); 662 | 663 | template 664 | static auto check(U* u) -> decltype(u->foo()); 665 | 666 | public: 667 | static constexpr bool value = 668 | IsSame 669 | < 670 | void, 671 | decltype(HasFoo::check((T*) nullptr)) 672 | >::value; 673 | }; 674 | ``` 675 | 676 | ```c++ 677 | hasFoo::value == true; 678 | hasFoo::value == false; 679 | ``` 680 | 681 | #### type_traits 682 | 683 | В стандартной библиотеки функции определения свойств типов is_* находятся в заголовочном файле type_traits 684 | 685 | Примеры: 686 | 687 | ```c++ 688 | is_integral // Является ли тип целочисленным 689 | is_floating_point // Является ли тип типом с плавающей точкой 690 | is_array // Является ли тип типом массива 691 | is_const // Содержит ли тип в себе квалификатор const 692 | is_pod // Является ли тип POD-типом 693 | has_virtual_destructor // Имеет ли виртуальный деструктор 694 | 695 | // И так далее 696 | ``` 697 | 698 | ### Шаблоны свойств (traits) 699 | 700 | ```c++ 701 | template 702 | struct NumericTraits 703 | { 704 | }; 705 | 706 | template <> // Специализация 707 | struct NumericTraits 708 | { 709 | static constexpr int64_t min = -128; 710 | static constexpr int64_t max = 127; 711 | }; 712 | ``` 713 | 714 | ```c++ 715 | template 716 | class Calculator 717 | { 718 | T getNumber(const std::string& text) 719 | { 720 | const int64_t value = toNumber(text); 721 | if (value < NumericTraits::min 722 | || value > NumericTraits::max) 723 | { 724 | // range error 725 | } 726 | } 727 | }; 728 | ``` 729 | 730 | > Смотрите заголовочный файл numeric_limits 731 | 732 | Не только значения, но и типы: 733 | 734 | ```c++ 735 | template 736 | class BasicStream 737 | { 738 | public: 739 | using Char = T; 740 | }; 741 | 742 | using Utf8Stream = BasicStream; 743 | 744 | Utf8Stream::Char c; 745 | ``` 746 | 747 | ### Классы стратегий 748 | 749 | Класс стратегий - интерфейс для применения стратегий в алгоритме. 750 | 751 | ```c++ 752 | class Json 753 | { 754 | public: 755 | void encode(const char* data, size_t size) {} 756 | }; 757 | 758 | class Xml 759 | { 760 | public: 761 | void encode(const char* data, size_t size) {} 762 | }; 763 | 764 | template 765 | class Connector 766 | { 767 | Format format_; 768 | public: 769 | void connect() 770 | { 771 | auto packet = makeConnectPacket(); 772 | auto encodedPacket = format_.encode( 773 | packet.data, packet.size); 774 | send(encodedPacket); 775 | } 776 | }; 777 | 778 | template 779 | using JsonConnector = Connector; 780 | ``` 781 | 782 | ### Отличия между свойствами и стратегиями 783 | 784 | Свойство - отличительная особенность характеризующая сущность. 785 | 786 | Стратегия - образ действия сущности. 787 | 788 | ### Сравнение динамического и статического полиморфизма 789 | 790 | ```c++ 791 | class Device 792 | { 793 | public: 794 | virtual ~Device() {} 795 | 796 | virtual void write(const char* data, size_t size) = 0; 797 | }; 798 | 799 | class File final 800 | : public Device 801 | { 802 | public: 803 | void write(const char* data, size_t size) override {} 804 | }; 805 | 806 | class Stream 807 | { 808 | Device* device_; 809 | public: 810 | explicit Stream(Device* device) 811 | : device_(device) 812 | { 813 | } 814 | 815 | void putChar(char c) 816 | { 817 | device_->write(&c, 1); 818 | } 819 | }; 820 | 821 | auto stream = Stream(new File("file.txt")); 822 | ``` 823 | ```c++ 824 | class File 825 | { 826 | public: 827 | explicit File(const char* name) {} 828 | void write(const char* data, size_t size) {} 829 | }; 830 | 831 | template 832 | class Stream 833 | { 834 | Device device_; 835 | public: 836 | explicit Stream(Device&& device) 837 | : device_(std::move(device)) 838 | { 839 | } 840 | 841 | void putChar(char c) 842 | { 843 | device_.write(&c, 1); 844 | } 845 | }; 846 | 847 | using FileStream = BasicStream; 848 | 849 | FileStream stream(File("data")); 850 | ``` 851 | 852 | 1. Динамический полиморфизм более гибок и позволяет настраивать поведение во время выполнения, но имеет накладные расходы на вызов виртуальных методов 853 | 2. Статический полиморфизм не имеет накладных расходов, но менее гибок 854 | 855 | #### Шаблоны с произвольным количеством аргументов (variadic templates) 856 | 857 | ```c++ 858 | print(1, "abc", 2.5); 859 | ``` 860 | 861 | ```c++ 862 | template 863 | void print(T&& val) 864 | { 865 | std::cout << val << '\n'; 866 | } 867 | 868 | template 869 | void print(T&& val, Args&&... args) 870 | { 871 | std::cout << val << '\n'; 872 | print(std::forward(args)...); 873 | } 874 | ``` 875 | 876 | ### Практическая часть 877 | 878 | Простой сериализатор поддерживающий два типа: uint64_t и bool. 879 | 880 | ```c++ 881 | struct Data 882 | { 883 | uint64_t a; 884 | bool b; 885 | uint64_t c; 886 | }; 887 | 888 | Data x { 1, true, 2 }; 889 | 890 | std::stringstream stream; 891 | 892 | Serializer serializer(stream); 893 | serializer.save(x); 894 | 895 | Data y { 0, false, 0 }; 896 | 897 | Deserializer deserializer(stream); 898 | const Error err = deserializer.load(y); 899 | 900 | assert(err == Error::NoError); 901 | 902 | assert(x.a == y.a); 903 | assert(x.b == y.b); 904 | assert(x.c == y.c); 905 | ``` 906 | 907 | Сериализовать в текстовый вид с разделением пробелом, bool сериализуется как true и false 908 | 909 | ##### Подсказки по реализации 910 | 911 | ```c++ 912 | struct Data 913 | { 914 | uint64_t a; 915 | bool b; 916 | uint64_t c; 917 | 918 | template 919 | Error serialize(Serializer& serializer) 920 | { 921 | return serializer(a, b, c); 922 | } 923 | }; 924 | ``` 925 | 926 | ```c++ 927 | // serializer.h 928 | #pragma once 929 | 930 | enum class Error 931 | { 932 | NoError, 933 | CorruptedArchive 934 | }; 935 | 936 | class Serializer 937 | { 938 | static constexpr char Separator = ' '; 939 | public: 940 | explicit Serializer(std::ostream& out) 941 | : out_(out) 942 | { 943 | } 944 | 945 | template 946 | Error save(T& object) 947 | { 948 | return object.serialize(*this); 949 | } 950 | 951 | template 952 | Error operator()(ArgsT... args) 953 | { 954 | return process(args...); 955 | } 956 | 957 | private: 958 | // process использует variadic templates 959 | }; 960 | ``` 961 | 962 | Deserializer реализуется аналогично Serializer, только принимает std::istream, а не std::ostream 963 | 964 | Пример десериализации bool: 965 | 966 | ```c++ 967 | Error load(bool& value) 968 | { 969 | std::string text; 970 | in_ >> text; 971 | 972 | if (text == "true") 973 | value = true; 974 | else if (text == "false") 975 | value = false; 976 | else 977 | return Error::CorruptedArchive; 978 | 979 | return Error::NoError; 980 | } 981 | ``` 982 | 983 | EOF 984 | -------------------------------------------------------------------------------- /09.threads.md: -------------------------------------------------------------------------------- 1 | ### std::function 2 | 3 | Обёртка функции общего назначения. Экземпляры std::function могут хранить и ссылаться на любой вызываемый объект - функцию, лямбда-выражение, привязку выражения или другой объект-функцию. Экземпляры std::function можно хранить в переменных, контейнерах, передавать в функции. 4 | 5 | ```c++ 6 | #include 7 | 8 | using Function = std::function; 9 | 10 | void doSomething(Function f) 11 | { 12 | f(10); 13 | } 14 | 15 | void foo(int x) {} 16 | 17 | Function f1 = foo; 18 | Function f2 = [](int x) {}; 19 | 20 | struct A 21 | { 22 | void operator()(int x) {} 23 | }; 24 | 25 | Function f3 = A(); 26 | 27 | struct B 28 | { 29 | void bar(int x) {} 30 | static void foo(int x) {} 31 | }; 32 | 33 | Function f4 = &B::foo; 34 | 35 | B b; 36 | Function f5 = std::bind( 37 | &B::bar, &b, std::placeholders::_1); 38 | 39 | std::vector functions = 40 | { f1, f2, f3, f4, f5 }; 41 | for (auto& f : functions) 42 | doSomething(f); 43 | ``` 44 | 45 | #### std::bind 46 | 47 | Позволяет получить функциональный объект с требуемым интерфейсом. 48 | 49 | ```c++ 50 | using Generator = std::function; 51 | 52 | void prepareData(Generator gen) { ... } 53 | 54 | int monotonic(int initial) { ... } 55 | int random(const std::string& device) { ... } 56 | 57 | Generator gen1 = std::bind(monotonic, 100); 58 | prepareData(gen1); 59 | 60 | Generator gen2 = std::bind(random, "/dev/random"); 61 | prepareData(gen2); 62 | ``` 63 | 64 | ```c++ 65 | if (std::all_of(v.begin(), v.end(), 66 | [](int x) { return x < 5; })) 67 | { 68 | ... 69 | } 70 | 71 | bool lessThan(int v, int max) 72 | { 73 | return v < max; 74 | } 75 | 76 | auto lessThan3 = 77 | std::bind(lessThan, std::placeholders::_1, 3); 78 | if (std::all_of(v.begin(), v.end(), lessThan3)) 79 | { 80 | ... 81 | } 82 | ``` 83 | 84 | ```c++ 85 | struct Robot 86 | { 87 | Robot() = default; 88 | Robot(const Robot&) = delete; 89 | Robot& operator=(const Robot&) = delete; 90 | }; 91 | 92 | using Command = std::function; 93 | 94 | enum class Direction 95 | { 96 | Left, 97 | Right, 98 | Up, 99 | Down 100 | }; 101 | 102 | void move(Robot& robot, Direction dir) { ... } 103 | void fire(Robot& robot) { ... } 104 | 105 | Robot robot; 106 | 107 | std::vector program; 108 | 109 | program.push_back( 110 | std::bind(move, robot, Direction::Left)); // error 111 | program.push_back( 112 | std::bind(fire, robot)); // error 113 | ``` 114 | 115 | ```c++ 116 | program.push_back( 117 | std::bind(move, std::ref(robot), Direction::Left)); 118 | program.push_back( 119 | std::bind(fire, std::ref(robot))); 120 | ``` 121 | 122 | ### Многопоточность (multithreading) 123 | 124 | ![](images/process.png) 125 | 126 | Многозадачность – возможность параллельной (или псевдопараллельной) обработки нескольких задач. 127 | 128 | 1. Многозадачность основанная на прерываниях планировщика 129 | 2. Кооперативная многозадачность – выполняемый код должен уступать процессорное время для других 130 | 131 | #### Современные компьютеры – сложные системы 132 | 133 | #### Компилятор 134 | 135 | С++ почти ничего не знает о многопоточности и при оптимизизациях не учитывает фактор многопоточности. 136 | 137 | ```c++ 138 | bool shutdown = false; 139 | 140 | void thread1() 141 | { 142 | shutdown = false; 143 | while (!shutdown) 144 | { 145 | // Может выполняться вечно 146 | } 147 | } 148 | 149 | void thread2() 150 | { 151 | shutdown = true; 152 | } 153 | ``` 154 | 155 | ```c++ 156 | volatile bool shutdown = false; 157 | // Поможет лишь частично! 158 | ``` 159 | 160 | #### Процессор 161 | 162 | ![](images/processor.png) 163 | 164 | ```c++ 165 | bool ready = false; 166 | int data = 0; 167 | 168 | int foo() { return 5; } 169 | 170 | void produce() 171 | { 172 | data = foo(); 173 | ready = true; 174 | } 175 | 176 | void consume() 177 | { 178 | while (!ready) ; 179 | assert(data == 5); // не всегда 180 | } 181 | ``` 182 | 183 | Вожможный пример выполнения кода процессором: 184 | 185 | ```c++ 186 | void produce() 187 | { 188 | // data = foo(); 189 | // Это долго, выполню пока это: 190 | ready = true; 191 | // А теперь остальное: 192 | data = foo(); 193 | } 194 | 195 | void consume() 196 | { 197 | while (!ready) // ждем 198 | ; 199 | assert(data == 5); // не всегда 200 | } 201 | ``` 202 | 203 | Исправляем produce: 204 | 205 | ```c++ 206 | void produce() 207 | { 208 | data = foo(); 209 | // Инструкция запрещающая процессору 210 | // изменять порядок выполнения 211 | ------------------ 212 | ready = true; 213 | } 214 | 215 | void consume() 216 | { 217 | // Этих данных у меня еще нет 218 | // while (!ready) 219 | // ; 220 | // Поэтому, поскольку данные не 221 | // взаимосвязаны, можно выполнить 222 | assert(data == 5); // не всегда 223 | // А теперь 224 | while (!ready) // ждем 225 | ; 226 | } 227 | ``` 228 | 229 | Исправляем consume: 230 | 231 | ```c++ 232 | void consume() 233 | { 234 | while (!ready) // ждем 235 | ; 236 | // Инструкция запрещающая процессору 237 | // изменять порядок выполнения 238 | ------------------ 239 | assert(data == 5); 240 | } 241 | ``` 242 | 243 | Барьер - инструкция состоящая из указания двух типов операций работы с памятью: 244 | ``` 245 | X_Y 246 | ``` 247 | 248 | Барьер гарантирует, что до барьера все операции работы с памятью типа X будут выполнены, а операции типа Y после барьера не начнут выполняться. 249 | 250 | Операций работы с памятью две: 251 | 252 | 1. Чтение (Load) 253 | 2. Запись (Store) 254 | 255 | Следовательно барьеров может быть 4: 256 | 257 | ![](images/barriers.png) 258 | 259 | 260 | ##### Acquire 261 | 262 | Acquire гарантирует, что все операции после барьера будут начаты после того, как будут выполнены все Load-операции до барьера. 263 | 264 | ##### Release 265 | 266 | Release гарантирует, что все операции до барьера будут выполнены до того, как начнут выполняться Store-операции после барьера. 267 | 268 | #### Барьеры памяти в C++ 269 | 270 | ```c++ 271 | // In namespace std 272 | 273 | enum memory_order 274 | { 275 | memory_order_relaxed, 276 | memory_order_consume, 277 | memory_order_acquire, // <-- acquire 278 | memory_order_release, // <-- release 279 | memory_order_acq_rel, 280 | memory_order_seq_cst // <-- default 281 | }; 282 | 283 | extern "C" void atomic_thread_fence(std::memory_order order) noexcept; 284 | ``` 285 | 286 | ```c++ 287 | void produce() 288 | { 289 | data = foo(); 290 | // Перед тем, как делать Store-операции 291 | // завершить все операции до барьера 292 | std::atomic_thread_fence(std::memory_order_release); 293 | ready = true; 294 | } 295 | ``` 296 | 297 | ```nasm 298 | produce(): 299 | call foo() 300 | mov DWORD PTR data[rip], eax 301 | mfence 302 | mov BYTE PTR ready[rip], 1 303 | ret 304 | ``` 305 | 306 | ```c++ 307 | void consume() noexcept 308 | { 309 | while (!ready) ; 310 | // Не выполнять никаких инструкций, пока 311 | // не будут выполнены Load-инструкции 312 | std::atomic_thread_fence(std::memory_order_acquire); 313 | int k = data; 314 | } 315 | ``` 316 | 317 | Инструкции amd64 реализующие барьеры: 318 | 319 | - LFENCE (load fence) 320 | - SFENCE (store fence) 321 | - MFENCE (memory fence) 322 | 323 | ##### mfence выполняется до микросекунды и более! 324 | 325 | Для контраста из второй лекции: 326 | 327 | ``` 328 | Compress 1K bytes with Zippy 3,000 ns 329 | ``` 330 | 331 | #### Атомарные значения 332 | 333 | ```c++ 334 | std::atomic value; 335 | 336 | T load(std::memory_order 337 | order = std::memory_order_seq_cst) const noexcept; 338 | 339 | void store(T value, std::memory_order 340 | order = std::memory_order_seq_cst) noexcept; 341 | ``` 342 | 343 | ```c++ 344 | std::atomic i = 5; 345 | 346 | i.store(3); 347 | int j = i.load(); 348 | 349 | ++i; 350 | int k = i; 351 | ``` 352 | 353 | ### Основная рекомендация 354 | 355 | ##### Не разделять изменяемые данные между потоками! 356 | 357 | ### Создание потока 358 | 359 | ```c++ 360 | #include 361 | 362 | void threadFunction() 363 | { 364 | ... 365 | } 366 | 367 | std::thread t(threadFunction); 368 | 369 | t.join(); или t.detach(); 370 | ``` 371 | 372 | ```c++ 373 | { 374 | std::thread t(threadFunction); 375 | } // <-- Здесь созданный на стеке t будет уничтожен 376 | ``` 377 | 378 | > Если на момент уничтожения объекта std::thread не был вызван join или detach, то будет вызван std::terminate 379 | 380 | > У каждого потока свой стек 381 | 382 | #### std::this_thread 383 | 384 | ```c++ 385 | // идентификатор потока 386 | const std::thread::id id = 387 | std::this_thread::get_id(); 388 | 389 | // указание планировщику снять 390 | // поток с выполнения до следующего раза 391 | std::this_thread::yield(); 392 | 393 | // усыпить поток на указанное время 394 | std::this_thread::sleep_for( 395 | std::chrono::seconds(1)) 396 | ``` 397 | 398 | > std::thread::id можно сравнить, можно вывести в поток вывода 399 | 400 | #### std::async 401 | 402 | ```c++ 403 | #include 404 | 405 | // запуск в отдельном потоке 406 | std::async(std::launc::async, []() { ... }); 407 | 408 | // запуск на усмотрение компилятора, может выполнится в том же потоке 409 | std::async(std::launc::deferred, []() { ... }); 410 | 411 | void doSomething(int x, int y) 412 | { 413 | } 414 | 415 | std::async(std::launch::async, doSomething, 5, 7); 416 | ``` 417 | 418 | 419 | #### std::future 420 | 421 | Ожидание выполнения асинхронной задачи. 422 | 423 | ```c++ 424 | std::future f = 425 | std::async(std::launch::async, []() { return 5 }); 426 | ... 427 | const int result = f.get(); 428 | ``` 429 | 430 | ```c++ 431 | auto f = 432 | std::async(std::launch::async, []() { return 5 }); 433 | ... 434 | f.wait(); 435 | ``` 436 | 437 | ```c++ 438 | auto f = 439 | std::async(std::launch::async, []() { return 5 }); 440 | 441 | auto status = f.wait_for(std::chrono::seconds(1)); 442 | 443 | if (status == std::future_status::deferred) 444 | std::cout << "задача еще не стартовала"; 445 | else if (status == std::future_status::timeout) 446 | std::cout << "результата не дождались"; 447 | else if (status == std::future_status::ready) 448 | std::cout << "все готово"; 449 | ``` 450 | 451 | #### std::promise 452 | 453 | Позволяет вернуть результат работы из потока. 454 | 455 | ```c++ 456 | #include 457 | 458 | std::future runTask() 459 | { 460 | std::promise promise; 461 | std::future future = promise.get_future(); 462 | 463 | auto task = [](std::promise&& p) 464 | { 465 | p.set_value(1); 466 | }; 467 | 468 | std::thread thread(task, std::move(promise)); 469 | thread.detach(); 470 | 471 | return future; 472 | } 473 | 474 | auto task = runTask(); 475 | task.get(); 476 | ``` 477 | 478 | #### Исключения в потоке 479 | 480 | ```c++ 481 | void foo() 482 | { 483 | throw std::runtime_error(); 484 | } 485 | 486 | std::thread t1(foo); 487 | t1.join(); 488 | ``` 489 | 490 | В этом случае поток просто завершиться, об исключении мы не узнаем. 491 | 492 | ```c++ 493 | auto f = std::async(std::launch::async, foo); 494 | 495 | try 496 | { 497 | f.get(); 498 | } 499 | catch (const std::runtime_error& error) 500 | { 501 | // Получили ошибку 502 | } 503 | ``` 504 | 505 | ```c++ 506 | auto task = ([](std::promise&& p) 507 | { 508 | try 509 | { 510 | foo(); 511 | } 512 | catch (...) 513 | { 514 | p.set_exception(std::current_exception()); 515 | } 516 | } 517 | ``` 518 | 519 | #### std::packaged_task 520 | 521 | ```c++ 522 | std::future runTask() 523 | { 524 | std::packaged_task task([]() 525 | { 526 | return 1; 527 | }); 528 | 529 | auto future = task.get_future(); 530 | 531 | std::thread thread(std::move(task)); 532 | thread.detach(); 533 | 534 | return future; 535 | } 536 | 537 | auto task = runTask(); 538 | task.get(); 539 | ``` 540 | 541 | ### Гонки (race condition) 542 | 543 | ```c++ 544 | int i = 17; 545 | 546 | void plus1() 547 | { 548 | i += 1; 549 | } 550 | 551 | std::thread t1(plus1); 552 | std::thread t2(plus1); 553 | 554 | t1.join(); 555 | t2.join(); 556 | 557 | std::cout << i; // ??? 558 | ``` 559 | 560 | ![](images/race.png) 561 | 562 | ### Средства синхронизации 563 | 564 | 1. Атомарные операции 565 | 2. Спинлоки (spinlock) 566 | 3. Семафоры (semaphore) 567 | 4. Мютексы (mutex) 568 | 5. Условные переменные (condition variable) 569 | 6. Критические секции (critical section) 570 | 7. Высокоуровневые очереди и планировщики 571 | 572 | #### Спинлоки (spinlock) 573 | 574 | База - все блокировки в ядре ОС основаны на спинлоках, которые в свою очередь используют атомарные операции, без этого реализовать безопасное межпроцессорное взаимодействие невозможно. 575 | 576 | ```c++ 577 | int atomicExchange(int* old, int newValue); 578 | 579 | // *lock == 0 - никем не захвачен 580 | void spinlock(volatile int* lock) 581 | { 582 | while (true) 583 | { 584 | if (*lock == 0) 585 | { 586 | const int old = atomicExchange(lock, 1); 587 | if (old == 0) 588 | { 589 | return; 590 | } 591 | } 592 | } 593 | } 594 | ``` 595 | 596 | #### Семафоры (semaphore) 597 | 598 | Семафор — это объект, над которым можно выполнить три операции: 599 | 600 | 1. Инициализация семафора (задать начальное значение счётчика) 601 | 2. Захват семафора (ждать пока счётчик станет больше 0, после этого уменьшить счётчик на единицу) 602 | 3. Освобождение семафора (увеличить счётчик на единицу) 603 | 604 | Реализуется ОС, описан в POSIX, на базе семафора можно реализовать остальные механизмы синхронизации. 605 | 606 | #### Мютексы (mutex) 607 | 608 | ```c++ 609 | #include 610 | 611 | std::mutex m; 612 | 613 | m.lock(); 614 | m.unlock(); 615 | 616 | if (m.try_lock()) 617 | m.unlock(); 618 | ``` 619 | 620 | ```c++ 621 | int i = 0; 622 | 623 | std::mutex mutex; 624 | 625 | void plus1() 626 | { 627 | std::lock_guard lock(mutex); 628 | i += 1; 629 | } 630 | 631 | std::thread t1(plus1); 632 | std::thread t2(plus1); 633 | ``` 634 | 635 | ![](images/mutex.png) 636 | 637 | ###### recursive_mutex 638 | 639 | ```c++ 640 | std::mutex m; 641 | m.lock(); 642 | m.lock(); // Неопределенное поведение 643 | ``` 644 | 645 | ```c++ 646 | std::recursive_mutex m; 647 | m.lock(); 648 | m.lock(); // Ок 649 | ``` 650 | 651 | > Количество lock и unlock должно совпадать 652 | 653 | ###### timed_mutex 654 | 655 | ```c++ 656 | #include 657 | 658 | std::timed_mutex m; 659 | 660 | m.lock(); 661 | m.unlock(); 662 | 663 | if (m.try_lock()) 664 | m.unlock(); 665 | 666 | auto period = std::chrono::milliseconds(100); 667 | if (m.try_lock_for(period)) 668 | m.unlock(); 669 | 670 | auto now = std::chrono::steady_clock::now(); 671 | m.try_lock_until(now + std::chrono::seconds(1)); 672 | ``` 673 | 674 | > steady_clock - monotonic clock 675 | 676 | ###### ```lock_guard``` 677 | 678 | Захват мютекса в конструкторе, освобождение в деструкторе. 679 | 680 | ###### ```unique_lock``` 681 | 682 | Расширяет поведение lock_guard: 683 | 684 | - lock 685 | - try_lock 686 | - try_lock_for 687 | - try_lock_until 688 | - unlock 689 | - swap 690 | - release 691 | 692 | ### Взаимоблокировки (deadlock) 693 | 694 | ```c++ 695 | std::mutex m1; 696 | std::mutex m2; 697 | 698 | void t1() // thread 1 699 | { 700 | std::lock_guard lock1(m1); 701 | std::lock_guard lock2(m2); 702 | } 703 | 704 | void t2() // thread 2 705 | { 706 | std::lock_guard lock1(m2); 707 | std::lock_guard lock2(m1); 708 | } 709 | ``` 710 | 711 | ![](images/deadlock.png) 712 | 713 | ##### Блокировка в одном и том же порядке 714 | 715 | ```c++ 716 | void t1() // thread 1 717 | { 718 | std::lock_guard lock1(m1); 719 | std::lock_guard lock2(m2); 720 | } 721 | 722 | void t2() // thread 2 723 | { 724 | std::lock_guard lock1(m1); 725 | std::lock_guard lock2(m2); 726 | } 727 | ``` 728 | 729 | ##### Одновременная блокировка 730 | 731 | Иногда дать гарантию на блокировку в одном и том же порядке дать нельзя. 732 | 733 | ```c++ 734 | class Data 735 | { 736 | std::mutex m_; 737 | public: 738 | void apply(const Data& data) 739 | { 740 | std::lock_guard lock1(m_); 741 | std::lock_guard lock2(data.m_); 742 | ... 743 | } 744 | }; 745 | 746 | Data d1; 747 | Data d2; 748 | 749 | d1.apply(d2); // thread 1 750 | d2.apply(d1); // thread 2 751 | ``` 752 | 753 | ```c++ 754 | void apply(const Data& data) 755 | { 756 | using Lock = std::unique_lock; 757 | Lock lock1(m_, std::defer_lock); 758 | Lock lock2(data.m_, std::defer_lock); 759 | std::lock(lock1, lock2); 760 | ... 761 | } 762 | ``` 763 | 764 | #### Условные переменные (condition_variable) 765 | 766 | Средство для обеспечения коммуникации потоков. 767 | 768 | ```c++ 769 | Data data; 770 | 771 | std::mutex m; 772 | std::condition_variable dataReady; 773 | 774 | void consumer() // thread 1 775 | { 776 | std::unique_lock lock(m); 777 | while (!data.ready()) 778 | dataReady.wait(lock); 779 | } 780 | 781 | void producer() // thread 2 782 | { 783 | { 784 | std::lock_guard lock(m); 785 | data.prepare(); 786 | } 787 | dataReady.notify_one(); 788 | } 789 | ``` 790 | 791 | ```c++ 792 | #include 793 | 794 | std::mutex m; 795 | std::unique_lock lock(m); 796 | 797 | std::condition_variable c; 798 | 799 | c.wait(lock); 800 | 801 | c.wait(lock, predicate); 802 | // while (!predicate()) 803 | // { 804 | // wait(lock); 805 | // } 806 | 807 | // wait_for 808 | // wait_until 809 | 810 | // В другом потоке 811 | c.notify_one(); 812 | c.notify_all(); 813 | ``` 814 | 815 | ### Семафор на базе мютекса 816 | 817 | ```c++ 818 | class Semaphore 819 | { 820 | std::mutex mutex_; 821 | std::condition_variable condition_; 822 | int counter_; 823 | public: 824 | explicit Semaphore(int initialValue = 1) 825 | : counter_(initialValue) 826 | { 827 | } 828 | 829 | void enter() 830 | { 831 | std::unique_lock lock(mutex_); 832 | condition_.wait(lock, 833 | [this]() 834 | { 835 | return counter_ > 0; 836 | }); 837 | --counter_; 838 | } 839 | 840 | void leave() 841 | { 842 | std::unique_lock lock(mutex_); 843 | ++counter_; 844 | condition_.notify_one(); 845 | } 846 | }; 847 | ``` 848 | 849 | ```c++ 850 | Semaphore s; 851 | 852 | void t1() // thread 1 853 | { 854 | s.enter(); 855 | s.leave(); 856 | } 857 | ``` 858 | 859 | ### Пул потоков (thread pool) 860 | 861 | Создание потока - дорогая операция, поэтому иногда хочется этого избежать. 862 | 863 | Идея: 864 | 865 | 1. Создаем N потоков, каждый поток либо выполняет задачу, либо спит 866 | 2. Новая задача добавляется в очередь, при этом происходит оповещение спящих потоков 867 | 3. Проснувшись поток проверяет, что в очереди есть задача, после чего извлекает ее из очереди и выполняет 868 | 4. Если задачи нет, поток засыпает ожидая оповещения 869 | 870 | ### Практическая часть 871 | 872 | Реализовать пул потоков со следующим интерфейсом: 873 | 874 | ```c++ 875 | class ThreadPool 876 | { 877 | public: 878 | explicit ThreadPool(size_t poolSize); 879 | 880 | // pass arguments by value 881 | template 882 | auto exec(Func func, Args... args) -> std::future; 883 | }; 884 | ``` 885 | 886 | Использование пула потоков: 887 | 888 | ```c++ 889 | struct A {}; 890 | 891 | void foo(const A&) {} 892 | 893 | ThreadPool pool(8); 894 | 895 | auto task1 = pool.exec(foo, A()); 896 | task1.get(); 897 | 898 | auto task2 = pool.exec([]() { return 1; }); 899 | task2.get(); 900 | ``` 901 | 902 | EOF 903 | -------------------------------------------------------------------------------- /07.exceptions.md: -------------------------------------------------------------------------------- 1 | ## Обработка ошибок 2 | 3 | 1. Возврат кода ошибки 4 | 2. Исключения 5 | 6 | ### Возврат кода ошибки 7 | 8 | ```c++ 9 | enum class Error 10 | { 11 | Success, 12 | Failure 13 | }; 14 | 15 | Error doSomething() 16 | { 17 | return Error::Success; 18 | } 19 | 20 | if (doSomething() != Error::Success) 21 | { 22 | showError(); 23 | } 24 | ``` 25 | 26 | \+ Простота 27 | 28 | \- Ошибку можно проигнорировать 29 | 30 | \- Делает код громозким 31 | 32 | ```c++ 33 | auto data = readData("data.json"); 34 | 35 | Json data; 36 | auto error = readData(data, "data.json"); 37 | if (error != Success) 38 | { 39 | ... 40 | } 41 | ``` 42 | 43 | #### Поддержка со стороны C++ 44 | 45 | ```c++ 46 | #include 47 | ``` 48 | 49 | ```c++ 50 | enum class HttpError 51 | { 52 | NoError = 200, 53 | NotFound = 404 54 | }; 55 | 56 | class HttpCategory: 57 | public std::error_category 58 | { 59 | public: 60 | const char* name() const noexcept override 61 | { 62 | return "http"; 63 | } 64 | 65 | std::string message(int code) const override 66 | { 67 | switch (code) 68 | { 69 | case 200: return "ok"; 70 | case 404: return "not found"; 71 | } 72 | assert(!"invalid error code"); 73 | } 74 | }; 75 | 76 | std::error_code make_error_code(HttpError error) 77 | { 78 | static const HttpCategory instance; 79 | return std::error_code( 80 | static_cast(error), 81 | instance); 82 | } 83 | ``` 84 | 85 | ```c++ 86 | std::error_code download(const std::string& url) 87 | { 88 | return make_error_code(HttpError::NotFound); 89 | } 90 | 91 | const auto error = download("http://1.1.1.1"); 92 | if (error) 93 | { 94 | std::cerr << error << '\n'; 95 | std::cerr << error.message() << '\n'; 96 | } 97 | ``` 98 | 99 | ``` 100 | http:404 101 | not found 102 | ``` 103 | 104 | ### Исключения 105 | 106 | \- Вопросы производительности 107 | 108 | \- При неправильном использовании могут усложнить программу 109 | 110 | \+ Нельзя проигнорировать 111 | 112 | ```c++ 113 | struct Error 114 | { 115 | std::string message_; 116 | const char* fileName_; 117 | int line_; 118 | Error(const std::string& message, 119 | const char* fileName, int line) 120 | : message_(message) 121 | , fileName_(fileName) 122 | , line_(line) 123 | { 124 | } 125 | }; 126 | 127 | void doSomething() 128 | { 129 | throw Error( 130 | "doSomething error", __FILE__, __LINE__); 131 | } 132 | 133 | try 134 | { 135 | doSomething(); 136 | } 137 | catch (const Error& error) 138 | { 139 | showError(); 140 | } 141 | ``` 142 | 143 | #### Что такое исключительная ситуация? 144 | 145 | Ошибка которую нельзя обработать на данном уровне и игнорирование которой делает дальнейшую работу программы бессмысленной. 146 | 147 | #### Гарантии безопасности исключений (exception safety) 148 | 149 | 1. Гарантировано искючений нет (No-throw guarantee) 150 | 151 | Операции всегда завершаются успешно, если исключительная ситуация возникла она обрабатывается внутри операции. 152 | 153 | 2. Строгая гарантия (Strong exception safety) 154 | 155 | Также известна как коммит ролбек семантика (commit/rollback semantics). Операции могут завершиться неудачей, но неудачные операции гарантированно не имеют побочных эффектов, поэтому все данные сохраняют свои исходные значения. 156 | 157 | ```c++ 158 | std::vector source = ...; 159 | try 160 | { 161 | std::vector tmp = source; 162 | tmp.push_back(getNumber()); 163 | tmp.push_back(getNumber()); <-- Исключение 164 | tmp.push_back(getNumber()); 165 | source.swap(tmp); 166 | } 167 | catch (...) 168 | { 169 | return; 170 | } 171 | ``` 172 | 173 | 3. Базовая гарантия (Basic exception safety) 174 | 175 | Выполнение неудачных операций может вызвать побочные эффекты, но все инварианты сохраняются и нет утечек ресурсов (включая утечку памяти). Любые сохраненные данные будут содержать допустимые значения, даже если они отличаются от того, что они были до исключения. 176 | 177 | ```c++ 178 | source.push_back(getNumber()); 179 | source.push_back(getNumber()); <-- Исключение 180 | source.push_back(getNumber()); 181 | ``` 182 | 183 | 4. Никаких гарантий (No exception safety) 184 | 185 | #### Поиск подходящего обработчика 186 | 187 | ```c++ 188 | class Error {}; 189 | 190 | class ArgumentError : public Error 191 | { 192 | std::string message_; 193 | public: 194 | ArgumentError(std::string&& message); 195 | const std::string& getMessage() const; 196 | }; 197 | 198 | File openFile(const std::string& name) 199 | { 200 | if (name.empty()) 201 | throw ArgumentError("empty file name"); 202 | } 203 | 204 | try 205 | { 206 | auto file = openFile("data.json"); 207 | auto json = file.readAll(); 208 | } 209 | catch (const ArgumentError& error) 210 | { 211 | std::cerr << error.getMessage(); 212 | } 213 | catch (const Error& error) 214 | { 215 | } 216 | catch (...) 217 | { 218 | } 219 | ``` 220 | 221 | 1. Поиск подходящего обработчика идет в порядке следования обработчиков в коде 222 | 2. Полного соответствия типа не требуется, будет выбран первый подходящий обработчик 223 | 3. Если перехватывать исключение по значению, то возможна срезка до базового класса 224 | 4. Если наиболее общий обработчик идет раньше, то более специализированный обработчик никогда не будет вызван 225 | 5. Три точки - перехват любого исключения 226 | 227 | > Исключения ОС - не исключения С++, например, деление на ноль. Для их обработки нужно использовать средства предоставляемые конкретной платформой 228 | 229 | #### Раскрутка стека 230 | 231 | ```c++ 232 | struct A {}; 233 | struct Error {}; 234 | struct FileError : public Error {}; 235 | 236 | void foo() 237 | { 238 | A a1; 239 | throw Error(); 240 | } 241 | 242 | void bar() 243 | { 244 | A a2; 245 | try 246 | { 247 | A a3; 248 | foo(); 249 | } 250 | catch (const FileError&) 251 | { 252 | } 253 | } 254 | 255 | bar(); 256 | ``` 257 | 258 | Поиск подходящего обработчика вниз по стеку вызовов с вызовом деструкторов локальных объектов - раскрутка стека. 259 | 260 | Если подходящий обработчик не был найден вызывается стандартная функция terminate. 261 | 262 | #### terminate 263 | 264 | Вызывает стандартную функцию С - abort. 265 | 266 | abort - аварийное завершение программы, деструкторы объектов вызваны не будут. 267 | 268 | Поведение terimnate можно изменить установив свой обработчик функцией set_terminate. 269 | 270 | ##### Где уместен catch (...)? 271 | 272 | Только в main, для того, чтобы поймать необработанное исключение, чтобы избежать вызов terminate и таким образом завершить работу с вызовом деструкторов. 273 | 274 | ```c++ 275 | int main() 276 | { 277 | try 278 | { 279 | ... 280 | } 281 | catch (...) 282 | { 283 | std::cerr << "unknown error"; 284 | } 285 | } 286 | ``` 287 | 288 | #### Перезапуск исключения 289 | 290 | ```c++ 291 | try 292 | { 293 | foo(); 294 | } 295 | catch (...) 296 | { 297 | std::cerr << "something wrong"; 298 | throw; 299 | } 300 | ``` 301 | 302 | #### noexcept 303 | 304 | ```c++ 305 | void foo() noexcept 306 | { 307 | } 308 | ``` 309 | 310 | noexcept говорит компилятору, что функция не выбрасывает исключений - это позволяет компилятору генерировать более компактный код, но если фактически исключение было выброшено, то будет вызвана функция terminate. 311 | 312 | #### Исключения в деструкторе 313 | 314 | Исключение покинувшее деструктор во время раскрутки стека или у глобального/статического объекта приведет к вызову terminate. 315 | 316 | Начиная с С++11 все деструкторы компилятором воспринимаются как помеченные noexcept - теперь исключения не должны покидать деструктора никогда. 317 | 318 | #### Исключения в конструкторе 319 | 320 | Клиент либо получает объект в консистентном состоянии, либо не получает ничего. 321 | 322 | ```c++ 323 | class Socket 324 | { 325 | static constexpr size_t BufferSize = 2048; 326 | char* buffer_; 327 | public: 328 | explicit Socket(const std::string& address) 329 | : buffer_(new char[BufferSize]) // <- утечка 330 | { 331 | if (address.empty()) 332 | throw ArgumentError(); 333 | } 334 | 335 | ~Socket() 336 | { 337 | delete[] buffer_; // Не будет вызван 338 | } 339 | }; 340 | ``` 341 | 342 | Для полностью сконструированных на момент выброса исключения объектов будут вызваны деструкторы, память выделенная под объект будет корректно освобождена, но поскольку объект не был полностью сконструирован, то деструктор вызван не будет. 343 | 344 | ### Стандартные классы рекомендуемые для исключений 345 | 346 | ```c++ 347 | #include 348 | ``` 349 | ![](images/exceptions.jpg) 350 | 351 | ```c++ 352 | class exception 353 | { 354 | public: 355 | explicit exception(char const* const message); 356 | 357 | virtual char const* what() const; 358 | ``` 359 | 360 | #### Управление ресурсами 361 | 362 | Используем идеому RAII (Resource Acquire Is Initialization): 363 | 364 | ```c++ 365 | struct Buffer 366 | { 367 | explicit Buffer(size_t size) 368 | : data_(new char[size]) 369 | { 370 | } 371 | 372 | ~Buffer() 373 | { 374 | delete[] data_; 375 | } 376 | 377 | char* data_; 378 | }; 379 | ``` 380 | 381 | ```c++ 382 | class Socket 383 | { 384 | static constexpr size_t BufferSize = 2048; 385 | Buffer buffer_; 386 | public: 387 | explicit Socket(const std::string& address) 388 | : buffer_(BufferSize) 389 | { 390 | if (address.empty()) 391 | throw ArgumentError(); 392 | } 393 | }; 394 | ``` 395 | 396 | ### Исключения под капотом 397 | 398 | ```c++ 399 | struct A 400 | { 401 | A() {} 402 | ~A() {} 403 | }; 404 | 405 | void bar() noexcept 406 | { 407 | } 408 | 409 | void foo() 410 | { 411 | A a; 412 | bar(); 413 | } 414 | ``` 415 | 416 | ```nasm 417 | A::A() [base object constructor]: 418 | ret 419 | A::~A() [base object destructor]: 420 | ret 421 | bar(): 422 | ret 423 | foo(): 424 | push rbp 425 | mov rbp, rsp 426 | sub rsp, 16 427 | lea rdi, [rbp - 8] 428 | call A::A() [base object constructor] 429 | call bar() 430 | lea rdi, [rbp - 8] 431 | call A::~A() [base object destructor] 432 | add rsp, 16 433 | pop rbp 434 | ret 435 | ``` 436 | 437 | ##### Убираем noexcept 438 | 439 | ```c++ 440 | struct A 441 | { 442 | A() {} 443 | ~A() {} 444 | }; 445 | 446 | void bar() {} 447 | 448 | void foo() 449 | { 450 | A a; 451 | bar(); 452 | } 453 | ``` 454 | 455 | ```nasm 456 | A::A() [base object constructor]: 457 | ret 458 | A::~A() [base object destructor]: 459 | ret 460 | bar(): 461 | ret 462 | foo(): 463 | call A::A() [base object constructor] 464 | call bar() 465 | jmp .LBB1_1 466 | .LBB1_1: 467 | call A::~A() [base object destructor] 468 | ret 469 | .LBB1_2: # landing pad 470 | call A::~A() [base object destructor] 471 | call _Unwind_Resume 472 | ``` 473 | 474 | ```c++ 475 | void _Unwind_Resume(struct _Unwind_Exception * object); 476 | ``` 477 | 478 | Появился специальный блок (landing pad) используемый при раскрутке стека. 479 | 480 | ##### Добавляем блок catch 481 | 482 | ```c++ 483 | struct A 484 | { 485 | A() {} 486 | ~A() {} 487 | }; 488 | 489 | void bar() {} 490 | 491 | void baz() noexcept {} 492 | 493 | void foo() 494 | { 495 | A a; 496 | try 497 | { 498 | bar(); 499 | } 500 | catch (...) 501 | { 502 | baz(); 503 | } 504 | } 505 | ``` 506 | 507 | ```nasm 508 | foo(): 509 | call A::A() [base object constructor] 510 | call bar() 511 | jmp .LBB2_1 512 | .LBB2_1: 513 | jmp .LBB2_5 514 | .LBB2_2: 515 | call __cxa_begin_catch 516 | call baz() 517 | call __cxa_end_catch 518 | jmp .LBB2_4 519 | .LBB2_4: 520 | jmp .LBB2_5 521 | .LBB2_5: 522 | call A::~A() [base object destructor] 523 | ret 524 | .LBB2_6: 525 | call A::~A() [base object destructor] 526 | call _Unwind_Resume 527 | ``` 528 | 529 | ##### Выбрасываем исключение 530 | 531 | ```c++ 532 | struct A 533 | { 534 | A() {} 535 | ~A() {} 536 | }; 537 | 538 | void bar() 539 | { 540 | throw A(); 541 | } 542 | 543 | void baz() noexcept 544 | { 545 | } 546 | 547 | void foo() 548 | { 549 | A a; 550 | try 551 | { 552 | bar(); 553 | } 554 | catch (...) 555 | { 556 | baz(); 557 | } 558 | } 559 | ``` 560 | 561 | ```nasm 562 | bar(): 563 | call __cxa_allocate_exception 564 | call A::A() [base object constructor] 565 | jmp .LBB0_1 566 | .LBB0_1: 567 | call __cxa_throw 568 | .LBB0_2: # landing pad 569 | call __cxa_free_exception 570 | call _Unwind_Resume 571 | foo(): 572 | call A::A() [base object constructor] 573 | call bar() 574 | jmp .LBB4_1 575 | .LBB4_1: 576 | jmp .LBB4_5 577 | .LBB4_2: 578 | call __cxa_begin_catch 579 | call baz() 580 | call __cxa_end_catch 581 | jmp .LBB4_4 582 | .LBB4_4: 583 | jmp .LBB4_5 584 | .LBB4_5: 585 | call A::~A() [base object destructor] 586 | ret 587 | .LBB4_6: 588 | call A::~A() [base object destructor] 589 | call _Unwind_Resume 590 | typeinfo name for A: 591 | .asciz "1A" 592 | typeinfo for A: 593 | .quad vtable for __cxxabiv1::__class_type_info+16 594 | .quad typeinfo name for A 595 | ``` 596 | 597 | ```c++ 598 | void __cxa_throw( 599 | void* thrown_exception, 600 | struct std::type_info * tinfo, 601 | void (*dest)(void*)); 602 | ``` 603 | 604 | ##### Компиляция с включенной оптимизацией 605 | 606 | ```c++ 607 | struct A 608 | { 609 | A() {} 610 | ~A() {} 611 | }; 612 | 613 | void bar(int x) 614 | { 615 | if (x == 1) 616 | throw A(); 617 | } 618 | 619 | void baz() noexcept 620 | { 621 | } 622 | 623 | void foo(int x) 624 | { 625 | A a; 626 | try 627 | { 628 | bar(x); 629 | } 630 | catch (...) 631 | { 632 | baz(); 633 | } 634 | } 635 | ``` 636 | 637 | ```nasm 638 | bar(int): 639 | cmp edi, 1 640 | je .LBB0_2 641 | ret 642 | .LBB0_2: 643 | call __cxa_allocate_exception 644 | call __cxa_throw 645 | foo(int): 646 | cmp edi, 1 647 | je .LBB3_1 648 | ret 649 | .LBB3_1: 650 | call __cxa_allocate_exception 651 | call __cxa_throw 652 | .LBB3_3: 653 | call __cxa_begin_catch 654 | jmp __cxa_end_catch # TAILCALL 655 | typeinfo name for A: 656 | .asciz "1A" 657 | typeinfo for A: 658 | .quad vtable for __cxxabiv1::__class_type_info+16 659 | .quad typeinfo name for A 660 | ``` 661 | 662 | ### Управление памятью 663 | 664 | Стандартная библиотека предлагает два умных указателя для автоматического управления памятью: 665 | 666 | 1. unique_ptr 667 | 2. shared_ptr / weak_ptr 668 | 669 | #### unique_ptr 670 | 671 | - Монопольное владение памятью, в конструкторе захват, в деструкторе освобождение 672 | - Копирование запрещено, перемещение разрешено 673 | 674 | ```c++ 675 | std::unique_ptr x(new MyClass()); 676 | auto y = std::make_unique(); // C++14 677 | 678 | std::unique_ptr z(new char[1024]); 679 | ``` 680 | 681 | #### shared_ptr 682 | 683 | - Совместное владение памятью 684 | - Копирование увеличивает счетчик ссылок 685 | - В деструкторе счетчик уменьшается и если становится равным 0, то объект уничтожается 686 | 687 | ```c++ 688 | std::shared_ptr x(new MyClass()); 689 | auto y = std::make_shared(); 690 | ``` 691 | 692 | #### Точки следования (sequence points) 693 | 694 | Точки следования - это точки в программе, где состояние реальной программы полностью соответствует состоянию следуемого из исходного кода. 695 | 696 | Точки следования необходимы для того, чтобы компилятор мог делать оптимизацию кода. 697 | 698 | ```c++ 699 | // Может быть утечка 700 | foo( 701 | std::shared_ptr(new MyClass()), 702 | bar()); 703 | ``` 704 | 705 | Компилятор может заменить это выражение на следующее: 706 | 707 | ```c++ 708 | auto tmp1 = new MyClass(); 709 | auto tmp2 = bar(); 710 | auto tmp3 = std::shared_ptr(tmp1); 711 | foo(tmp1, tmp3); 712 | ``` 713 | 714 | Если из bar вылетит исключение, то объект на который указывает tmp1 будет некому удалить. 715 | 716 | Решение 1: 717 | 718 | ```c++ 719 | std::shared_ptr x(new MyClass()); 720 | foo(x, bar()); // ok 721 | ``` 722 | 723 | Решение 2: 724 | 725 | ```c++ 726 | foo(std::make_shared(), bar()); // ok 727 | ``` 728 | 729 | Местонахождение точек: 730 | 731 | 1. В конце каждого полного выражения - ; 732 | 2. В точке вызова функции после вычисления всех аргументов 733 | 3. Сразу после возврата функции, перед тем как любой другой код из вызвавшей функции начал выполняться 734 | 4. После первого выражения (а) в следующих конструкциях: 735 | 736 | ```c++ 737 | a || b 738 | a && b 739 | a, b 740 | a ? b : c 741 | ``` 742 | 743 | Если программа пытается модифицировать одну переменную дважды не пересекая точку следования, то это ведет к неопределенному поведению (undefined behavior): 744 | 745 | ```c++ 746 | int x = 0; 747 | x = x++; // <-- UB 748 | 749 | int i = 0; 750 | i = i++ + ++i; // <-- UB 751 | ``` 752 | 753 | #### Схематичное устройство shared_ptr 754 | 755 | ```c++ 756 | #include 757 | #include 758 | 759 | template 760 | class SharedPtr 761 | { 762 | struct Data 763 | { 764 | T* object_; 765 | int counter_; 766 | }; 767 | 768 | Data* data_; 769 | 770 | void release() 771 | { 772 | --data_->counter_; 773 | if (data_->counter_ == 0) 774 | { 775 | delete data_->object_; 776 | delete data_; 777 | } 778 | } 779 | 780 | public: 781 | SharedPtr(T* object = nullptr) 782 | : data_(new Data { object, 1 }) 783 | { 784 | } 785 | 786 | ~SharedPtr() 787 | { 788 | release(); 789 | } 790 | 791 | SharedPtr(const SharedPtr& copied) 792 | : data_(copied.data_) 793 | { 794 | ++data_->counter_; 795 | } 796 | 797 | SharedPtr& operator=(const SharedPtr& copied) 798 | { 799 | if (data_ == copied.data_) 800 | return *this; 801 | 802 | release(); 803 | 804 | data_ = copied.data_; 805 | ++data_->counter_; 806 | return *this; 807 | } 808 | 809 | T& operator*() 810 | { 811 | return *data_->object_; 812 | } 813 | 814 | const T& operator*() const 815 | { 816 | return *data_->object_; 817 | } 818 | 819 | T* operator->() 820 | { 821 | return data_->object_; 822 | } 823 | 824 | const T* operator->() const 825 | { 826 | return data_->object_; 827 | } 828 | }; 829 | 830 | struct A 831 | { 832 | A() { std::cout << "A" << std::endl; } 833 | ~A() { std::cout << "~A" << std::endl; } 834 | void foo() { std::cout << this << std::endl; } 835 | }; 836 | 837 | SharedPtr foo(SharedPtr x) 838 | { 839 | return x; 840 | } 841 | 842 | int main() 843 | { 844 | auto x = foo(new A()); 845 | auto y = x; 846 | y->foo(); 847 | (*x).foo(); 848 | y = nullptr; 849 | return 0; 850 | } 851 | ``` 852 | 853 | ##### Предпочитайте make_shared 854 | 855 | ```c++ 856 | auto x = std::shared_ptr(new MyClass()); 857 | auto x = std::make_shared(); 858 | ``` 859 | 860 | 1. Нет дублирования (MyClass два раза) 861 | 2. Безопасно в вызове функций 862 | 3. Оптимально - 1 вызов new вместо 2 863 | 864 | ### Проблема циклических ссылок 865 | 866 | ![](images/shared_ptr.png) 867 | 868 | ```c++ 869 | class Widget; 870 | 871 | class Window 872 | { 873 | std::vector> children_; 874 | }; 875 | 876 | class Widget 877 | { 878 | std::shared_ptr parent_; 879 | }; 880 | ``` 881 | 882 | Winwow не может быть удален, так как в Widget жив shared_ptr на него, а Widget в свою очередь не может быть удален, так как жив Window. 883 | 884 | > Ключевой вопрос С++ - кто кем владеет 885 | 886 | #### weak_ptr 887 | 888 | ```c++ 889 | class Widget; 890 | 891 | class Window 892 | { 893 | std::vector> children_; 894 | }; 895 | 896 | class Widget 897 | { 898 | std::weak_ptr parent_; 899 | }; 900 | ``` 901 | 902 | weak_ptr не принимает владение объектом, но располагая weak_ptr всегда можно узнать жив ли объект и если жив, то получить на него shared_ptr. 903 | 904 | ```c++ 905 | std::shared_ptr x; 906 | std::weak_ptr weak = x; 907 | std::shared_ptr y = weak.lock(); 908 | if (y) 909 | { 910 | ... 911 | } 912 | ``` 913 | 914 | #### enable_shared_from_this 915 | 916 | Иногда нужно получить shared_ptr от самого себя, например, очень актуально при асинхронном взаимодействии, когда время жизни объекта не определено. 917 | 918 | ```c++ 919 | class A 920 | { 921 | std::shared_ptr getSharedPtr() 922 | { 923 | // Приведет к многократному удалению 924 | return std::shared_ptr(this); 925 | } 926 | }; 927 | ``` 928 | 929 | Решение: 930 | 931 | ```c++ 932 | class A 933 | : public std::enable_shared_from_this 934 | { 935 | std::shared_ptr getSharedPtr() 936 | { 937 | return shared_from_this(); // Ok 938 | } 939 | }; 940 | ``` 941 | 942 | ##### Ограничения enable_shared_from_this 943 | 944 | ```c++ 945 | class A 946 | : public std::enable_shared_from_this 947 | { 948 | A() 949 | { 950 | shared_from_this(); // throw std::bad_weak_ptr 951 | } 952 | 953 | ~A() 954 | { 955 | shared_from_this(); // throw std::bad_weak_ptr 956 | } 957 | }; 958 | ``` 959 | 960 | Также перед использованием shared_from_this на объект уже должен ссылаться shared_ptr: 961 | 962 | ```c++ 963 | auto a = std::make_shared(); 964 | auto b = a->getSharedPtr(); 965 | ``` 966 | 967 | #### Практическая часть 968 | 969 | Написать функцию для форматирования строки, поддерживаться должен любой тип, который может быть выведен в поток вывода. Формат строки форматирования: 970 | 971 | ``` 972 | "{0} any text {1} {0}" 973 | ``` 974 | 975 | Номер в фигурных скобках - номер аргумента. Если аргументов меньше, чем число в скобках, и в случае прочих ошибок выбрасывать исключение std::runtime_error 976 | 977 | Пример: 978 | 979 | ```c++ 980 | auto text = format("{1}+{1} = {0}", 2, "one"); 981 | assert(text == "one+one = 2"); 982 | ``` 983 | 984 | Фигурные скобки - зарезервированный символ, если встречаются вне контекста {n} выбрасывать исключение std::runtime_error 985 | 986 | EOF -------------------------------------------------------------------------------- /04.classes.md: -------------------------------------------------------------------------------- 1 | ### Структуры и классы 2 | 3 | #### Конструктор (ctor) 4 | 5 | Служит для инициализации объекта. 6 | 7 | Если конструктор не написан явно, С++ гарантирует, что будет создан конструктор по умолчанию. 8 | 9 | ```c++ 10 | struct A 11 | { 12 | A() {} 13 | }; 14 | ``` 15 | 16 | ##### Конструктор вызывается автоматически при создании объекта 17 | 18 | ```c++ 19 | // Выделение памяти в куче + вызов конструктора 20 | A* x = new A(); 21 | 22 | // Выделение памяти на стеке + вызов конструктора 23 | A y; 24 | ``` 25 | 26 | ### Деструктор (dtor) 27 | 28 | Если деструктор не написан явно, С++ гарантирует, что будет создан деструктор по умолчанию. 29 | 30 | ```c++ 31 | struct A 32 | { 33 | ~A() {} 34 | }; 35 | ``` 36 | 37 | Служит для деинициализации объекта, **гарантированно вызыватся при удалении объекта**. 38 | 39 | ```c++ 40 | { 41 | A* x = new A(); 42 | A y; 43 | } // Выход из области видимости: 44 | // вызов деструктора + освобождение 45 | // памяти на стеке 46 | // Для х это означает, что 47 | // будет освобождена только память 48 | // занятая указателем, но та, 49 | // на которую он указывает 50 | ``` 51 | 52 | ```c++ 53 | { 54 | A* x = new A(); 55 | A y; 56 | delete x; 57 | } 58 | ``` 59 | 60 | ### RAII (Resource Acquire Is Initialization) 61 | 62 | Захват ресурса есть инициализация. 63 | 64 | В конструкторе объект получает доступ к какому либо ресурсу (например, открывается файл), а при вызове деструктура этот ресурс освобождается (закрывается файл). 65 | 66 | ```c++ 67 | class File 68 | { 69 | public: 70 | File(const char* fileName) 71 | { 72 | descriptor = open(fileName, O_CREAT); 73 | } 74 | 75 | ~File() 76 | { 77 | close(descriptor); 78 | } 79 | }; 80 | ``` 81 | 82 | > Можно использовать не только для управления ресурсами 83 | 84 | ```c++ 85 | struct Profiler 86 | { 87 | Profiler() { // получаем текущее время } 88 | ~Profiler() { // сохраняем время между 89 | // выховами конструктора и деструктора } 90 | }; 91 | 92 | void someFunction() 93 | { 94 | Profiler p; 95 | if (...) return; 96 | ... 97 | if (...) return; 98 | ... 99 | } 100 | ``` 101 | 102 | ### Константные методы 103 | 104 | ```c++ 105 | struct A 106 | { 107 | int x; 108 | }; 109 | 110 | A a; 111 | a.x = 3; // Ок 112 | 113 | const A b; 114 | b.x = 3; // Ошибка, константный 115 | // объект нельзя изменять 116 | 117 | const A* c = &a; 118 | c->x = 3; // Ошибка, константный 119 | // объект нельзя изменять 120 | ``` 121 | 122 | Любые методы кроме конструктора и деструктора могут быть константными. 123 | 124 | ```c++ 125 | class User 126 | { 127 | using Year = uint32_t; 128 | Year age; 129 | public: 130 | void setAge(Year value) 131 | { 132 | age = value; 133 | } 134 | 135 | bool canBuyAlcohol() const 136 | { 137 | return age >= 21; 138 | } 139 | }; 140 | 141 | class UserDb 142 | { 143 | public: 144 | const User* getReadOnlyUser( 145 | const std::string& name) const 146 | { 147 | return db.find(name); 148 | } 149 | }; 150 | 151 | const User* user = userDb.getReadOnlyUser("Bob"); 152 | user->setAge(21); // Ошибка 153 | if (user->canBuyAlcohol()) // Ок 154 | ``` 155 | 156 | ```c++ 157 | void User_setAge([User* const this], Year value) 158 | { 159 | [this->]age = value; 160 | } 161 | 162 | bool User_canBuyAlcohol([const User* const this]) const 163 | { 164 | return [this->]age >= 21; 165 | } 166 | ``` 167 | 168 | ### mutable 169 | 170 | ```c++ 171 | class Log 172 | { 173 | void write(const std::string& text); 174 | }; 175 | 176 | class UserDb 177 | { 178 | mutable Log log; 179 | public: 180 | const User& getReadOnlyUser( 181 | const std::string& name) const 182 | { 183 | log.write("..."); 184 | return db.find(name); 185 | } 186 | }; 187 | ``` 188 | 189 | ```c++ 190 | const User& UserDb_getReadOnlyUser( 191 | [const UserDb* const this], 192 | const std::string& name) const 193 | { 194 | [this->]log.write("..."); 195 | // Вызываем Log_write с const Log* const 196 | } 197 | 198 | void Log_write([Log* const this], const std::string& text) 199 | { 200 | ... 201 | } 202 | ``` 203 | 204 | ### Перегрузка методов 205 | 206 | > Методы классов - это просто функции, в которые неявно передается указатель на сам класс 207 | 208 | ```c++ 209 | class Connection 210 | { 211 | public: 212 | void send(int value); 213 | void send(const std::string& value); 214 | }; 215 | ``` 216 | 217 | > Конструкторы - это тоже функции и их тоже можно перегружать. 218 | 219 | ```c++ 220 | class Connection 221 | { 222 | public: 223 | Connection(const std::string& address, uint16_t port); 224 | Connection(const IpV4& address, uint16_t port); 225 | Connection(const IpV6& address, uint16_t port); 226 | }; 227 | ``` 228 | 229 | > Деструкторы - тоже функции, но перегружать нельзя. 230 | 231 | ### Параметры по умолчанию 232 | 233 | ```c++ 234 | class Connection 235 | { 236 | public: 237 | Connection(const std::string& address, uint16_t port = 8080); 238 | }; 239 | ``` 240 | ```c++ 241 | class Connection 242 | { 243 | public: 244 | Connection(const std::string& address = "localhost", uint16_t port = 8080); 245 | }; 246 | ``` 247 | 248 | > Пропусков в параметрах по умолчанию быть не должно, начинаться они могут не с первого аргумента, но заканчиваться должны на последнем. 249 | 250 | ### Наследование 251 | 252 | Возможность порождать класс на основе другого с сохранением всех свойств класса-предка. 253 | 254 | Класс, от которого производится наследование, называется базовым, родительским или суперклассом. Новый класс – потомком, наследником, дочерним или производным классом. 255 | 256 | ```c++ 257 | class Shape 258 | { 259 | protected: 260 | int x; 261 | int y; 262 | }; 263 | 264 | class Circle 265 | : public Shape 266 | { 267 | int radius; 268 | }; 269 | ``` 270 | 271 | > Наследование моделирует отношение «является». 272 | 273 | > Требуется для создания иерархичности – свойства реального мира. 274 | 275 | ### Представление в памяти при наследовании 276 | 277 | #### Инструменты для исследования 278 | 279 | - **sizeof(T)** - размер типа в байтах 280 | - **offsetof(T, M)** - смещение поля M от начала типа T 281 | 282 | ```c++ 283 | struct A 284 | { 285 | double x; 286 | }; 287 | 288 | struct B 289 | : public A 290 | { 291 | double y; 292 | }; 293 | 294 | struct C 295 | : public B 296 | { 297 | double z; 298 | }; 299 | 300 | std::cout << sizeof(A) << std::endl; // 8 301 | std::cout << sizeof(B) << std::endl; // 16 302 | std::cout << sizeof(C) << std::endl; // 24 303 | 304 | std::cout << offsetof(C, x) << std::endl; // 0 305 | std::cout << offsetof(C, y) << std::endl; // 8 306 | std::cout << offsetof(C, z) << std::endl; // 16 307 | ``` 308 | 309 | | Поле | Смещение | Доступность в типах | 310 | |---|---|---| 311 | | x | 0 | A, B, C | 312 | | y | 8 | B, C | 313 | | z | 16 | C | 314 | 315 | ```c++ 316 | C* c = new C(); 317 | c->x; // Ок 318 | c->y; // Ок 319 | c->z; // Ок 320 | 321 | B* b = (B*) c; 322 | b->x; // Ок 323 | b->y; // Ок 324 | b->z; // Ошибка компиляции 325 | 326 | A* a = (A*) c; 327 | a->x; // Ок 328 | a->y; // Ошибка компиляции 329 | a->z; // Ошибка компиляции 330 | ``` 331 | 332 | #### Приведение вверх и вниз по иерархии 333 | 334 | ##### Приведение вверх (к базовому классу) всегда безопасно. 335 | 336 | ```c++ 337 | void foo(A& a) {} 338 | 339 | C c; 340 | foo(c); 341 | ``` 342 | 343 | ##### Приведение вниз может быть опасным 344 | 345 | ```c++ 346 | struct A {}; 347 | struct B : public A {}; 348 | struct C : public A {}; 349 | ``` 350 | 351 | ![](images/abc.png) 352 | 353 | ``` 354 | B* b = new B(); 355 | A* a = b; 356 | C* c = a; // Ошибка компиляции 357 | C* c = static_cast(b); // Ошибка компиляции 358 | C* c = static_cast(a); // !!! 359 | ``` 360 | 361 | > Сохраняйте тип, пусть компилятор помогает писать корректный код! 362 | 363 | > Общий базовый тип - плохая идея 364 | 365 | ### Композиция 366 | 367 | ```c++ 368 | class Car 369 | { 370 | Engine engine; 371 | Wheels wheels[4]; 372 | }; 373 | ``` 374 | > Композиция моделирует отношение «содержит/является частью» 375 | 376 | ### Агрегация 377 | 378 | ```c++ 379 | class Car 380 | { 381 | Driver* driver_; 382 | }; 383 | ``` 384 | 385 | При агрегации класс не контролирует время жизни своей части. 386 | 387 | ### Унифицированный язык моделирования (Unified Modeling Language, UML) 388 | 389 | UML – это открытый стандарт, использующий графические обозначения для создания абстрактной модели системы, называемой UML-моделью. UML используется для визуализации и документирования программных систем. UML не является языком программирования, но на основании UML-моделей возможна генерация кода. 390 | 391 | UML редактор: [https://www.draw.io/](https://www.draw.io/) 392 | 393 | ### Диаграмма классов (Class diagram) 394 | 395 | Статическая структурная диаграмма, описывающая структуру системы, демонстрирующая классы системы, их атрибуты, методы и зависимости между классами. 396 | 397 | #### Классы 398 | 399 | Видимость: 400 | 401 | ``` 402 | + Публичный метод (public) 403 | # Защищенный метод (protected) 404 | - Приватный метод (private) 405 | ``` 406 | 407 | ![](images/uml-class.png) 408 | 409 | #### Ассоциация 410 | 411 | Показывает, что объекты связаны, бывает однонаправленной и двунаправленной. 412 | 413 | ![](images/uml-association.png) 414 | 415 | #### Композиция 416 | 417 | Моделирует отношение «содержит/является частью». 418 | 419 | При композиции класс явно контролирует время жизни своей составной части. 420 | 421 | ![](images/uml-composition.png) 422 | 423 | #### Агрегация 424 | 425 | Моделирует отношение «содержит/является частью». 426 | 427 | При агрегации класс не контролирует время жизни своей части. 428 | 429 | ![](images/uml-aggregation.png) 430 | 431 | #### Наследование 432 | 433 | Моделирует отношение «является». 434 | 435 | ![](images/uml-inheritance.png) 436 | 437 | ### Конструирование объекта 438 | 439 | Порядок конструирования: 440 | 1. Выделяется память под объект 441 | 2. Если есть базовые классы, то конструирование начинается с них в порядке их очередности в списке наследования 442 | 3. Инициализируются поля класса в том порядке, в котором они объявлены в классе 443 | 4. Происходит вызов конструктора 444 | 445 | ```c++ 446 | class A 447 | { 448 | public: 449 | A() {} // 3 450 | ~A() {} 451 | 452 | private: 453 | int x; // 1 454 | int y; // 2 455 | }; 456 | 457 | class B 458 | : public A 459 | { 460 | public: 461 | B() {} // 5 462 | ~B() {} 463 | 464 | private: 465 | int z; // 4 466 | }; 467 | ``` 468 | 469 | Порядок уничтожения: 470 | 1. Происходит вызов деструктора 471 | 2. Вызываются деструкторы для полей класса в обратном порядке их объявления в классе 472 | 3. Уничтожаются базовые классы в порядке обратном списку наследования 473 | 474 | ```c++ 475 | class A 476 | { 477 | public: 478 | A() {} 479 | ~A() {} // 3 480 | 481 | private: 482 | int x; // 5 483 | int y; // 4 484 | }; 485 | 486 | class B 487 | : public A 488 | { 489 | public: 490 | B() {} 491 | ~B() {} // 1 492 | 493 | private: 494 | int z; // 2 495 | }; 496 | ``` 497 | 498 | ### Списки инициализации 499 | 500 | ```c++ 501 | class A 502 | { 503 | A() 504 | : x(5) 505 | , y(6) 506 | { 507 | z = 7; 508 | } 509 | 510 | int x; 511 | int y; 512 | int z; 513 | }; 514 | ``` 515 | 516 | Распространенная ошибка: 517 | 518 | ```c++ 519 | class A 520 | { 521 | A() 522 | : y(5) // Инициализация в порядке объявления в классе! 523 | , x(y) 524 | { 525 | } 526 | 527 | int x; 528 | int y; 529 | }; 530 | ``` 531 | 532 | ### Инициализация в объявлении 533 | 534 | ```c++ 535 | class A 536 | { 537 | int x = 3; 538 | }; 539 | ``` 540 | 541 | ### Выравнивание полей 542 | 543 | В целях повышения быстродействия данные в памяти должны быть выровнены, то есть размещены определенным образом. 544 | 545 | Предпочтительное выравнивание можно узнать: 546 | 547 | ```c++ 548 | std::cout << alignof(char) << std::endl; // 1 549 | std::cout << alignof(double) << std::endl; // 8 550 | ``` 551 | 552 | ##### Гранулярность памяти 553 | 554 | ![](images/granularity.png) 555 | 556 | #### Инструменты для исследования 557 | 558 | - **sizeof(T)** - размер типа в байтах 559 | - **offsetof(T, M)** - смещение поля M от начала типа T 560 | 561 | ```c++ 562 | struct S 563 | { 564 | char m1; 565 | double m2; 566 | }; 567 | ``` 568 | 569 | ```c++ 570 | sizeof(char) == 1 571 | sizeof(double) == 8 572 | sizeof(S) == 16 573 | offsetof(S, m1) == 0 574 | offsetof(S, m2) == 8 575 | ``` 576 | 577 | ``` 578 | [ char ][ double ] 579 | [c][.][.][.][.][.][.][.][d][d][d][d][d][d][d][d] 580 | ``` 581 | 582 | Выравниванием можно управлять: 583 | ```c++ 584 | #pragma pack(push, 1) 585 | class S 586 | { 587 | public: 588 | char m1; 589 | double m2; 590 | }; 591 | #pragma pack(pop) 592 | 593 | offsetof(S, m1) == 0 594 | offsetof(S, m2) == 1 595 | sizeof(S) == 9 596 | ``` 597 | 598 | > Работать будет не всегда, компилятор может это проигнорировать, если посчитает, что сделать это нельзя 599 | 600 | #### Оптимизация размера POD структур 601 | 602 | ```c++ 603 | struct POD 604 | { 605 | int x; 606 | double y; 607 | int z; 608 | }; 609 | 610 | std::cout << sizeof(POD) << std::endl; // 24 611 | ``` 612 | ```c++ 613 | struct POD 614 | { 615 | double y; 616 | int x; 617 | int z; 618 | }; 619 | 620 | std::cout << sizeof(POD) << std::endl; // 16 621 | ``` 622 | 623 | ### Простые типы (POD, Plain Old Data) 624 | 625 | 1. Скалярные типы (bool, числа, указатели, перечисления (enum), nullptr_t) 626 | 2. class или struct которые: 627 | - Имеют только тривиальные (сгенерированные компилятором) конструктор, деструктор, конструктор копирования 628 | - Нет виртуальных функций и базового класса 629 | - Все нестатические поля с модификатором доступа public 630 | - Не содержит статических полей не POD типа 631 | 632 | ### Примеры 633 | 634 | ```c++ 635 | class NotPOD 636 | { 637 | public: 638 | NotPOD(int x) 639 | { 640 | } 641 | }; 642 | ``` 643 | 644 | ```c++ 645 | class NotPOD 646 | : public Base 647 | { 648 | }; 649 | ``` 650 | 651 | ```c++ 652 | class NotPOD 653 | { 654 | virtual void f() 655 | { 656 | } 657 | }; 658 | ``` 659 | 660 | ```c++ 661 | class NotPOD 662 | { 663 | int x; 664 | }; 665 | ``` 666 | 667 | ```c++ 668 | class POD 669 | { 670 | public: 671 | NotPOD m1; 672 | int m2; 673 | static double m3; 674 | private: 675 | void f() {} 676 | }; 677 | ``` 678 | 679 | > Копирование простого типа - memcpy 680 | 681 | > Простые типы можно использовать для передачи из программы в программу, записи на диск и т.д. Но только на одной и той же платформе! 682 | 683 | ```c++ 684 | struct POD 685 | { 686 | int x; 687 | double y; 688 | 689 | void serialize(File& file) const 690 | { 691 | file.write(this, sizeof(POD)); 692 | } 693 | }; 694 | ``` 695 | 696 | ### Инициализация POD типов 697 | 698 | ```c++ 699 | struct POD 700 | { 701 | int x; 702 | double y; 703 | }; 704 | ``` 705 | 706 | Инициализация нулем (zero-initialization): 707 | 708 | ```c++ 709 | POD p1 = POD(); 710 | POD p2 {}; 711 | POD* p3 = new POD(); 712 | 713 | // x == 0 714 | // y == 0 715 | ``` 716 | 717 | Инициализация по умолчанию (default-initialization): 718 | 719 | ```c++ 720 | POD p1; 721 | POD* p2 = new POD; 722 | 723 | // x, y содержат мусор 724 | ``` 725 | 726 | ### Неявные приведения типов 727 | 728 | ```c++ 729 | int x = 5; 730 | double y = x; 731 | ``` 732 | 733 | ```c++ 734 | struct A 735 | { 736 | A(int x) {} 737 | }; 738 | 739 | A a = 5; 740 | ``` 741 | ```c++ 742 | struct A 743 | { 744 | A(int x, int y = 3) {} 745 | }; 746 | 747 | A a = 5; 748 | ``` 749 | ```c++ 750 | class BigInt 751 | { 752 | public: 753 | BigInt(int64_t value) {} 754 | }; 755 | 756 | BigInt x = 5; 757 | ``` 758 | 759 | ```c++ 760 | struct A 761 | { 762 | explicit A(int x) {} 763 | }; 764 | 765 | A a = 5; // Ошибка 766 | ``` 767 | 768 | ### Операторы 769 | 770 | ##### Операторы сравнения 771 | 772 | ```c++ 773 | class BigInt 774 | { 775 | static constexpr size_t Size = 256; 776 | uint8_t data_[Size]; 777 | public: 778 | bool operator==(const BigInt& other) const 779 | { 780 | if (this == &other) 781 | return true; 782 | 783 | for (size_t i = 0; i < Size; ++i) 784 | if (data_[i] != other.data_[i]) 785 | return false; 786 | 787 | return true; 788 | } 789 | 790 | bool operator!=(const BigInt& other) const 791 | { 792 | return !(*this == other); 793 | } 794 | }; 795 | 796 | BigInt x = 5; 797 | 798 | if (c == 5) 799 | ... 800 | ``` 801 | 802 | Еще операторы сравнения: 803 | - Меньше < 804 | - Больше > 805 | - Меньше или равно <= 806 | - Больше или равно >= 807 | 808 | ##### Бинарные арифметические операторы 809 | 810 | Попробуем написать метод осуществлющий сложение двух BigInt: 811 | 812 | ```c++ 813 | class BigInt 814 | { 815 | const BigInt& operator+(BigInt& other) 816 | { 817 | ... 818 | return *this; 819 | } 820 | }; 821 | 822 | BigInt x = 3; 823 | BigInt y = 5; 824 | BigInt z = x + y; // ок 825 | BigInt z = x + y + x; // ошибка 826 | ``` 827 | 828 | ```c++ 829 | // x + y -> const BigInt 830 | tmp = x.operator+(y) 831 | // tmp + z 832 | tmp.operator+(x) 833 | // opearator - не константный метод, 834 | // a tmp - константный объект 835 | ``` 836 | 837 | ```c++ 838 | class BigInt 839 | { 840 | BigInt& operator+(BigInt& other) 841 | { 842 | ... 843 | return *this; 844 | } 845 | }; 846 | 847 | BigInt x = 3; 848 | BigInt y = 5; 849 | BigInt z = x + y + x; // ок, но x изменился 850 | ``` 851 | 852 | ```c++ 853 | class BigInt 854 | { 855 | BigInt operator+(BigInt& other) 856 | { 857 | BigInt tmp; 858 | ... 859 | return tmp; 860 | } 861 | }; 862 | 863 | 864 | BigInt x = 3; 865 | BigInt y = 5; 866 | BigInt z = x + y + x; // ок 867 | ``` 868 | 869 | ```c++ 870 | BigInt x = 3; 871 | const BigInt y = 5; 872 | BigInt z = x + y + x; // передача константного 873 | // объекта y по неконстантной ссылке 874 | ``` 875 | 876 | ```c++ 877 | class BigInt 878 | { 879 | BigInt operator+(const BigInt& other) 880 | { 881 | BigInt tmp; 882 | ... 883 | return tmp; 884 | } 885 | }; 886 | 887 | BigInt x = 3; 888 | const BigInt y = 5; 889 | BigInt z = x + y + x; // ок 890 | ``` 891 | 892 | ```c++ 893 | const BigInt x = 3; 894 | const BigInt y = 5; 895 | BigInt z = x + y + x; // передача константного 896 | // объекта x по неконстантной ссылке 897 | ``` 898 | 899 | Финальный вариант: 900 | 901 | ```c++ 902 | class BigInt 903 | { 904 | BigInt operator+(const BigInt& other) const 905 | { 906 | BigInt tmp; 907 | ... 908 | return tmp; 909 | } 910 | }; 911 | ``` 912 | 913 | Операторы могут не быть членами класса: 914 | 915 | ```c++ 916 | class Int128 {}; 917 | class BigInt 918 | { 919 | BigInt operator+(const Int128& other) const 920 | { 921 | ... 922 | } 923 | }; 924 | BigInt x = 3; 925 | Int128 y = 5; 926 | BigInt z = x + y; // ok 927 | BigInt z = y + x; // у Int128 нет оператора 928 | // сложения с BigInt 929 | ``` 930 | 931 | ```c++ 932 | class BigInt 933 | { 934 | friend BigInt operator+(const Int128& x, const BigInt& y); 935 | }; 936 | 937 | BigInt operator+(const Int128& x, const BigInt& y) 938 | { 939 | ... 940 | } 941 | ``` 942 | 943 | Еще операторы: 944 | - Вычитание - 945 | - Деление / 946 | - Умножение * 947 | - Остаток от деления % 948 | 949 | > Для операторов действует стандартный приоритет арифметических операторов 950 | 951 | ##### Унарные арифметические операторы 952 | 953 | ```c++ 954 | BigInt x = 3; 955 | BigInt y = -x; 956 | 957 | class BigInt 958 | { 959 | bool isNegative_; 960 | public: 961 | BigInt operator-() const 962 | { 963 | BigInt tmp(*this); 964 | tmp.isNegative_ = !isNegative_; 965 | return tmp; 966 | } 967 | }; 968 | ``` 969 | 970 | Для симметрии есть унарный плюс. 971 | 972 | #### Операторы инкремента 973 | 974 | ```c++ 975 | Int x = 3; 976 | ++x; 977 | x++; 978 | 979 | class BigInt 980 | { 981 | void increment() 982 | { 983 | ... 984 | } 985 | public: 986 | // ++x 987 | BigInt& operator++() 988 | { 989 | increment(); 990 | return *this; 991 | } 992 | // x++ 993 | BigInt operator++(int) 994 | { 995 | BigInt tmp(*this); 996 | increment(); 997 | return tmp; 998 | } 999 | }; 1000 | ``` 1001 | 1002 | Операторы декремента аналогичны операторам инкремента. 1003 | 1004 | ##### Логические операторы 1005 | 1006 | - Отрицание ! (унарный) 1007 | - И (логическое умножение) && 1008 | - ИЛИ (логическое сложение) || 1009 | 1010 | ##### Битовые операторы 1011 | 1012 | - Инверсия ~ 1013 | - И & 1014 | - ИЛИ | 1015 | - Исключающее ИЛИ (xor) ^ 1016 | - Сдвиг влево << 1017 | - Сдвиг вправо >> 1018 | 1019 | ##### Составное присваивание 1020 | 1021 | Все арифметические, логические и побитовые операции только изменяющиие состояние объекта (с = в начале). 1022 | 1023 | ```c++ 1024 | x += 3; 1025 | x *= 4; 1026 | ``` 1027 | 1028 | ```c++ 1029 | class BigInt 1030 | { 1031 | // не константный метод, так как объект изменяется 1032 | void operator+=(const BigInt& other) 1033 | { 1034 | ... 1035 | } 1036 | }; 1037 | ``` 1038 | 1039 | > Не канон 1040 | 1041 | ``` 1042 | BigInt x = 3; 1043 | (x += 5) + 7; 1044 | 1045 | class BigInt 1046 | { 1047 | BigInt& operator+=(const BigInt& other) 1048 | { 1049 | ... 1050 | return *this; 1051 | } 1052 | }; 1053 | ``` 1054 | 1055 | ##### Оператор вывода в поток 1056 | 1057 | Не метод класса. 1058 | 1059 | ```c++ 1060 | std::ostream& operator<<(std::ostream& out, const BigInt& value) 1061 | { 1062 | out << ...; 1063 | return out; 1064 | } 1065 | 1066 | BigInt x = 5; 1067 | std::cout << x; 1068 | ``` 1069 | 1070 | ##### Операторы доступа 1071 | 1072 | Семантика доступа к массиву. 1073 | 1074 | ```c++ 1075 | class Array 1076 | { 1077 | uint8_t* data_; 1078 | public: 1079 | const uint8_t& operator[](size_t i) const 1080 | { 1081 | return data_[i]; 1082 | } 1083 | 1084 | uint8_t& operator[](size_t i) 1085 | { 1086 | return data_[i]; 1087 | } 1088 | }; 1089 | 1090 | Array a; 1091 | a[3] = 4; 1092 | 1093 | const Array b; 1094 | b[5] = 6; // Ошибка 1095 | auto x = b[1]; // Ok 1096 | ``` 1097 | 1098 | ##### Семантика указателя 1099 | 1100 | ```c++ 1101 | class MyObject 1102 | { 1103 | public: 1104 | void foo() {} 1105 | }; 1106 | 1107 | class MyObjectPtr 1108 | { 1109 | MyObject* ptr_; 1110 | public: 1111 | MyObjectPtr() 1112 | : ptr_(new MyObject()) 1113 | { 1114 | } 1115 | 1116 | ~MyObjectPtr() 1117 | { 1118 | delete ptr_; 1119 | } 1120 | 1121 | MyObject& operator*() 1122 | { 1123 | return *ptr_; 1124 | } 1125 | 1126 | const MyObject& operator*() const 1127 | { 1128 | return *ptr_; 1129 | } 1130 | 1131 | MyObject* operator->() 1132 | { 1133 | return ptr_; 1134 | } 1135 | 1136 | const MyObject* operator->() const 1137 | { 1138 | return ptr_; 1139 | } 1140 | }; 1141 | 1142 | MyObjectPtr p; 1143 | p->foo(); 1144 | (*p).foo(); 1145 | ``` 1146 | 1147 | ##### Функтор 1148 | 1149 | Позволяет работать с объектом как с функцией. 1150 | 1151 | ```c++ 1152 | class Less 1153 | { 1154 | public: 1155 | bool operator()( 1156 | const BigInt& left, const BigInt& right) const 1157 | { 1158 | return left < right; 1159 | } 1160 | }; 1161 | 1162 | Less less; 1163 | if (less(3, 5)) 1164 | ... 1165 | ``` 1166 | 1167 | ##### Другие операторы 1168 | 1169 | - new 1170 | - delete 1171 | - , 1172 | 1173 | ### Сокрытие 1174 | 1175 | ```c++ 1176 | struct A 1177 | { 1178 | void foo() {} // 1 1179 | }; 1180 | 1181 | struct B 1182 | : public A 1183 | { 1184 | void foo() {} // 2 1185 | }; 1186 | 1187 | A a; 1188 | a.foo(); // Будет вызвана 1 1189 | 1190 | B b; 1191 | b.foo(); // Будет вызвана 2 1192 | 1193 | A* c = new B(); 1194 | c->foo(); // Будет вызвана 1 1195 | ``` 1196 | 1197 | ### Виртуальные функции 1198 | 1199 | ```c++ 1200 | struct A 1201 | { 1202 | virtual void foo() const {} // 1 1203 | }; 1204 | 1205 | struct B 1206 | : public A 1207 | { 1208 | void foo() const override {} // 2 1209 | }; 1210 | 1211 | A a; 1212 | a.foo(); // Будет вызвана 1 1213 | 1214 | B b; 1215 | b.foo(); // Будет вызвана 2 1216 | 1217 | A* c = new B(); 1218 | c->foo(); // Будет вызвана 2 1219 | 1220 | const A& d = B(); 1221 | d.foo(); // Будет вызвана 2 1222 | ``` 1223 | 1224 | В первых двух случаях используется раннее (статическое) связывание, еще на этапе компиляции компилятор знает какой метод вызвать. 1225 | 1226 | В третьем случае используется позднее (динамическое) связывание, компилятор на этапе компиляции не знает какой метод вызвать, выбор нужного метода будет сделан во время выполнения. 1227 | 1228 | #### Виртуальные функции в С 1229 | 1230 | ```c++ 1231 | #include 1232 | 1233 | struct Device 1234 | { 1235 | virtual void write(const char* message) {} 1236 | }; 1237 | 1238 | class Console : public Device 1239 | { 1240 | int id_; 1241 | public: 1242 | Console(int id) 1243 | : id_(id) 1244 | { 1245 | } 1246 | 1247 | void write(const char* message) override 1248 | { 1249 | printf("Console %d: %s\n", id_, message); 1250 | } 1251 | }; 1252 | 1253 | class Socket : public Device 1254 | { 1255 | const char* address_; 1256 | public: 1257 | Socket(const char* address) 1258 | : address_(address) 1259 | { 1260 | } 1261 | 1262 | void write(const char* message) override 1263 | { 1264 | printf("Send %s to %s\n", message, address_); 1265 | } 1266 | }; 1267 | 1268 | int main() 1269 | { 1270 | Device* devices[] = { 1271 | new Console(10), 1272 | new Socket("10.0.0.1") }; 1273 | 1274 | Device* dev1 = devices[0]; 1275 | dev1->write("A"); 1276 | 1277 | Device* dev2 = devices[1]; 1278 | dev2->write("B"); 1279 | 1280 | return 0; 1281 | } 1282 | ``` 1283 | 1284 | ``` 1285 | Console 10: A 1286 | Send B to 10.0.0.1 1287 | ``` 1288 | 1289 | ```C 1290 | #include 1291 | #include 1292 | 1293 | struct Device; 1294 | 1295 | struct DeviceVirtualFunctionTable 1296 | { 1297 | void (*write)(Device* self, const char* message); 1298 | }; 1299 | 1300 | struct Device 1301 | { 1302 | DeviceVirtualFunctionTable vft_; 1303 | }; 1304 | 1305 | void Device_write(Device* self, const char* message) 1306 | { 1307 | self->vft_.write(self, message); 1308 | } 1309 | 1310 | struct Console 1311 | { 1312 | DeviceVirtualFunctionTable vft_; 1313 | int id_; 1314 | }; 1315 | 1316 | void Console_write(Device* self, const char* message) 1317 | { 1318 | Console* console = (Console*) self; 1319 | printf("Console %d: %s\n", console->id_, message); 1320 | } 1321 | 1322 | Device* Console_new(int id) 1323 | { 1324 | Console* instance = (Console*) malloc(sizeof(Console)); 1325 | instance->vft_.write = Console_write; 1326 | instance->id_ = id; 1327 | return (Device*) instance; 1328 | } 1329 | 1330 | struct Socket 1331 | { 1332 | DeviceVirtualFunctionTable vft_; 1333 | const char* address_; 1334 | }; 1335 | 1336 | void Socket_write(Device* self, const char* message) 1337 | { 1338 | Socket* socket = (Socket*) self; 1339 | printf("Send %s to %s\n", message, socket->address_); 1340 | } 1341 | 1342 | Device* Socket_new(const char* address) 1343 | { 1344 | Socket* instance = (Socket*) malloc(sizeof(Socket)); 1345 | instance->vft_.write = Socket_write; 1346 | instance->address_ = address; 1347 | return (Device*) instance; 1348 | } 1349 | 1350 | int main() 1351 | { 1352 | Device* devices[] = { 1353 | Console_new(10), 1354 | Socket_new("10.0.0.1") }; 1355 | 1356 | Device* dev1 = devices[0]; 1357 | Device_write(dev1, "A"); 1358 | 1359 | Device* dev2 = devices[1]; 1360 | Device_write(dev2, "B"); 1361 | 1362 | return 0; 1363 | } 1364 | ``` 1365 | 1366 | ``` 1367 | Console 10: A 1368 | Send B to 10.0.0.1 1369 | ``` 1370 | 1371 | ##### Таблица виртуальных функций 1372 | 1373 | Если в классе или в каком-либо его базовом классе есть виртуальная функция, то каждый объект хранит указатель на таблицу виртуальных функций. 1374 | 1375 | Таблица представляет собой массив из указателей на функции. 1376 | 1377 | ```c++ 1378 | struct A 1379 | { 1380 | void foo() {} 1381 | int x; 1382 | }; 1383 | 1384 | struct B 1385 | { 1386 | virtual void foo() {} 1387 | int x; 1388 | }; 1389 | 1390 | std::cout << sizeof(A) << '\n'; 1391 | std::cout << sizeof(B) << '\n'; 1392 | ``` 1393 | 1394 | ``` 1395 | 4 1396 | 16 1397 | ``` 1398 | 1399 | ##### Виртуальный деструктор 1400 | 1401 | ```c++ 1402 | struct A 1403 | { 1404 | ~A() 1405 | { 1406 | std::cout << "A"; 1407 | } 1408 | }; 1409 | 1410 | struct B 1411 | : public A 1412 | { 1413 | ~B() 1414 | { 1415 | std::cout << "B"; 1416 | delete object_; 1417 | } 1418 | 1419 | SomeObject* object_; 1420 | }; 1421 | 1422 | A* a = new B(); 1423 | delete a; 1424 | ``` 1425 | ``` 1426 | A 1427 | ``` 1428 | 1429 | > Произошла утечка, так как не был вызван деструктор, в котором мы освобождали ресурс. 1430 | 1431 | ```c++ 1432 | struct A 1433 | { 1434 | virtual ~A() 1435 | { 1436 | } 1437 | }; 1438 | ``` 1439 | 1440 | > Используете наследование? Сделайте деструктор виртуальным. 1441 | 1442 | ##### Чисто виртуальные функции (pure virtual) 1443 | 1444 | ```c++ 1445 | class Writer 1446 | { 1447 | public: 1448 | virtual void ~Writer() {} 1449 | 1450 | virtual void write(const char* message) = 0; 1451 | }; 1452 | 1453 | class ConsoleWriter 1454 | : public Writer 1455 | { 1456 | public: 1457 | void write(const char* message) override 1458 | { 1459 | std::cout << message; 1460 | } 1461 | } 1462 | ``` 1463 | 1464 | ##### Абстрактные классы 1465 | 1466 | Классы имеющие хоть одну чисто виртуальную функцию - абстрактные. При попытке создать их компилятор выдаст ошибку. Если в производном классе не сделать реализацию чисто виртуальной функции, то он тоже становится абстрактным. 1467 | 1468 | > Абстрактные классы в С++ - продвинутые интерфейсные классы в других языках. 1469 | 1470 | ##### Виртуальные функции и параметры по умолчанию 1471 | 1472 | ```c++ 1473 | struct A 1474 | { 1475 | virtual void foo(int i = 10) 1476 | { 1477 | std::cout << i; // 1 1478 | } 1479 | }; 1480 | 1481 | struct B 1482 | : public A 1483 | { 1484 | virtual void foo(int i = 20) 1485 | { 1486 | std::cout << i; // 2 1487 | } 1488 | }; 1489 | 1490 | A* a = new B(); 1491 | a->foo(); // Будет вызвана 2, вывод 10 1492 | 1493 | B* b = new B(); 1494 | b->foo(); // Будет вызвана 2, вывод 20 1495 | 1496 | A* a = new A(); 1497 | a->foo(); // Будет вызвана 1, вывод 10 1498 | ``` 1499 | 1500 | > Лучше избегать параметров по умолчанию для виртуальных функций 1501 | 1502 | ### Модификаторы доступа при наследовании 1503 | 1504 | ```c++ 1505 | class A 1506 | { 1507 | public: 1508 | int x_; 1509 | protected: 1510 | int y_; 1511 | private: 1512 | int z_; 1513 | }; 1514 | ``` 1515 | 1516 | Псевдокод! Поля базового класса после наследования имеют такие модификаторы: 1517 | 1518 | ```c++ 1519 | class B : public A 1520 | { 1521 | public: 1522 | int x_; 1523 | protected: 1524 | int y_; 1525 | }; 1526 | 1527 | A* a = new B(); // Ok 1528 | ``` 1529 | ```c++ 1530 | class B : protected A 1531 | { 1532 | protected: 1533 | int x_; 1534 | int y_; 1535 | }; 1536 | 1537 | A* a = new B(); // Ошибка 1538 | ``` 1539 | ```c++ 1540 | class B : private A 1541 | { 1542 | private: 1543 | int x_; 1544 | int y_; 1545 | }; 1546 | 1547 | A* a = new B(); // Ошибка 1548 | ``` 1549 | 1550 | ##### public - классическое ООП наследование 1551 | 1552 | ```c++ 1553 | class Device 1554 | { 1555 | }; 1556 | 1557 | class NetworkAdapter 1558 | : public Device 1559 | { 1560 | }; 1561 | 1562 | class DeviceManager 1563 | { 1564 | void addDevice(Device* dev) 1565 | { 1566 | } 1567 | } 1568 | 1569 | devManager.addDevice(new NetworkAdapter()); 1570 | ``` 1571 | 1572 | ##### private - наследование реализации 1573 | 1574 | ```c++ 1575 | class NetworkAdapter 1576 | : public Device 1577 | , private Loggable 1578 | { 1579 | }; 1580 | 1581 | Loggable* l = new NetworkAdapter(); // Ошибка 1582 | ``` 1583 | 1584 | #### final 1585 | 1586 | ```c++ 1587 | struct A final 1588 | { 1589 | }; 1590 | 1591 | struct B : public A // Ошибка 1592 | ``` 1593 | 1594 | ### Множественное наследование 1595 | 1596 | ```c++ 1597 | struct A 1598 | { 1599 | virtual ~A() {} 1600 | double x; 1601 | double y; 1602 | }; 1603 | 1604 | struct B : public A { }; 1605 | 1606 | struct C : public A { }; 1607 | 1608 | struct D 1609 | : public B 1610 | , public C 1611 | { 1612 | }; 1613 | ``` 1614 | 1615 | ``` 1616 | +------+ +------+ 1617 | | A | | A | 1618 | | x, y | | x, y | 1619 | | vtab | | vtab | 1620 | +------+ +------+ 1621 | ^ ^ 1622 | | | 1623 | +------+ +------+ 1624 | | B | | C | 1625 | +------+ +------+ 1626 | ^ ^ 1627 | | | 1628 | +------+ 1629 | | D | 1630 | +------+ 1631 | ``` 1632 | 1633 | ```c++ 1634 | // 2 * 8(double) + 1 * 8(vtable) 1635 | sizeof(A) == 24 1636 | sizeof(B) == 24 1637 | sizeof(C) == 24 1638 | // (2 * 8(double) + 1 * 8(vtable)) + (2 * 8(double) + 1 * 8(vtable)) 1639 | sizeof(D) == 48 1640 | ``` 1641 | 1642 | ``` 1643 | [A][B][D] 1644 | [A][C] 1645 | ``` 1646 | 1647 | ```c++ 1648 | struct A 1649 | { 1650 | A(double x) 1651 | : x(x) 1652 | , y(0) 1653 | { 1654 | } 1655 | virtual ~A() {} 1656 | double x; 1657 | double y; 1658 | }; 1659 | 1660 | struct B : public A 1661 | { 1662 | B(double x) 1663 | : A(x) 1664 | { 1665 | y = x * 2; 1666 | } 1667 | }; 1668 | 1669 | struct C : public A 1670 | { 1671 | C(double x) 1672 | : A(x) 1673 | { 1674 | y = x * 2; 1675 | } 1676 | }; 1677 | 1678 | struct D 1679 | : public B 1680 | , public C 1681 | { 1682 | D() 1683 | : B(2) 1684 | , C(3) 1685 | { 1686 | B::y = 1; 1687 | C::y = 2; 1688 | } 1689 | }; 1690 | ``` 1691 | 1692 | #### Ромбовидное наследование 1693 | 1694 | ```c++ 1695 | struct A 1696 | { 1697 | virtual ~A() {} 1698 | double x; 1699 | double y; 1700 | }; 1701 | 1702 | struct B : virtual public A { }; 1703 | 1704 | struct C : virtual public A { }; 1705 | 1706 | struct D 1707 | : public B 1708 | , public C 1709 | { 1710 | }; 1711 | ``` 1712 | 1713 | ``` 1714 | +------+ 1715 | | A | 1716 | | x, y | 1717 | | vtab | 1718 | +------+ 1719 | ^ ^ 1720 | | | 1721 | +------+ +------+ 1722 | | B | | C | 1723 | | vtab | | vtab | 1724 | +------+ +------+ 1725 | ^ ^ 1726 | | | 1727 | +------+ 1728 | | D | 1729 | +------+ 1730 | ``` 1731 | 1732 | ```c++ 1733 | // 2 * 8(double) + 1 * 8(vtable) 1734 | sizeof(A) == 24 1735 | // 2 * 8(double) + 2 * 8(vtable) 1736 | sizeof(B) == 32 1737 | sizeof(C) == 32 1738 | // 2 * 8(double) + 3 * 8(vtable) 1739 | sizeof(D) == 40 1740 | ``` 1741 | 1742 | ``` 1743 | [B][D] 1744 | [C] 1745 | [A] 1746 | ``` 1747 | 1748 | ### Вложенные классы 1749 | 1750 | ```c++ 1751 | class Vector 1752 | { 1753 | public: 1754 | class Iterator 1755 | { 1756 | }; 1757 | 1758 | private: 1759 | char* data_; 1760 | }; 1761 | 1762 | Vector::Iterator it = ... 1763 | ``` 1764 | 1765 | > Имеют доступ к закрытой части внешнего класса 1766 | 1767 | ### Практическая часть 1768 | 1769 | Нужно написать класс-матрицу, тип элементов int. В конструкторе задается количество рядов и строк. Поддерживаются оперции: получить количество строк(rows)/столбцов(columns), получить конкретный элемент, умножить на число(*=), сравнение на равенство/неравенство. В случае ошибки выхода за границы бросать исключение: 1770 | 1771 | ```c++ 1772 | throw std::out_of_range("") 1773 | ``` 1774 | 1775 | Пример: 1776 | 1777 | ```c++ 1778 | const size_t rows = 5; 1779 | const size_t cols = 3; 1780 | 1781 | Matrix m(rows, cols); 1782 | 1783 | assert(m.getRows() == 5); 1784 | assert(m.getColumns() == 3); 1785 | 1786 | m[1][2] = 5; // строка 1, колонка 2 1787 | double x = m[4][1]; 1788 | 1789 | m *= 3; // умножение на число 1790 | 1791 | Matrix m1(rows, cols); 1792 | 1793 | if (m1 == m) 1794 | { 1795 | } 1796 | ``` 1797 | 1798 | ##### Подсказка 1799 | 1800 | Чтобы реализовать семантику [][] понадобится прокси-класс. Оператор матрицы возращает другой класс, в котором тоже используется оператор [] и уже этот класс возвращает значение. 1801 | 1802 | EOF 1803 | -------------------------------------------------------------------------------- /08.stl.md: -------------------------------------------------------------------------------- 1 | ### Функтор (функциональный объект) 2 | 3 | Объект ведущий себя подобно функции. 4 | 5 | ```c++ 6 | template 7 | class Less 8 | { 9 | const T& x_; 10 | private: 11 | Less(const T& x) 12 | : x_(x) 13 | { 14 | } 15 | 16 | bool operator()(const T& y) const 17 | { 18 | return x_ < y; 19 | } 20 | }; 21 | 22 | Less lessThen3(3); 23 | 24 | bool result = lessThen3(5); // false 25 | ``` 26 | 27 | ### Лямбда-функция 28 | 29 | Краткая форма записи анонимных функторов. 30 | 31 | ```c++ 32 | auto lessThen3 = [](int y) { return 3 < y; }; 33 | 34 | bool result = lessThen3(5); // false 35 | ``` 36 | 37 | #### Лямбда - краткая форма анонимного функтора 38 | 39 | ```c++ 40 | int x = 3; 41 | auto add3 = [x](int y) { return x + y; }; 42 | auto s = add3(5); // 8 43 | ``` 44 | 45 | ```c++ 46 | class lambda__a123 // Сгенерированное имя 47 | { 48 | int x_; 49 | public: 50 | explicit sum(int x) 51 | : x_(x) 52 | { 53 | } 54 | 55 | int operator()(int y) const 56 | { 57 | return x_ + y; 58 | } 59 | }; 60 | auto add3 = lambda__a123(3); 61 | auto s = add3(5); // 8 62 | ``` 63 | 64 | ### Стандартная библиотека С++ 65 | 66 | 1. Ввод-вывод 67 | 2. Многопоточность 68 | 3. Регулярные выражения 69 | 4. Библиотека С 70 | 5. Библиотека шаблонов STL 71 | 6. Прочее (дата и время, обработка ошибок, поддержка локализации и т.д.) 72 | 73 | > Документация: [https://en.cppreference.com/w/](https://en.cppreference.com/w/) 74 | 75 | ### Потоки ввода-вывода 76 | 77 | ![](images/streams.png) 78 | 79 | ### Файловый ввод-вывод 80 | 81 | ```c++ 82 | #include 83 | ``` 84 | 85 | ##### std::ifstream 86 | 87 | Чтение из файла. 88 | 89 | ```c++ 90 | std::ifstream file("/tmp/file.txt"); 91 | if (!file) 92 | { 93 | std::cout << "can't open file" ; 94 | return; 95 | } 96 | 97 | while (file.good()) 98 | { 99 | std::string s; 100 | file >> s; 101 | } 102 | ``` 103 | 104 | ```c++ 105 | const int size = 1024; 106 | char buf[size]; 107 | 108 | std::ifstream file("/tmp/file.data", std::ios::binary); 109 | file.read(buf, size); 110 | const auto readed = file.gcount(); 111 | ``` 112 | 113 | ##### std::ofstream 114 | 115 | Запись в файл. 116 | 117 | ```c++ 118 | std::ofstream file("/tmp/file.txt"); 119 | if (!file) 120 | { 121 | std::cout << "can't open file" ; 122 | return; 123 | } 124 | 125 | file << "abc" << 123; 126 | ``` 127 | 128 | ```c++ 129 | const int size = 1024; 130 | char buf[size]; 131 | 132 | std::ofstream file("/tmp/file.data", std::ios::binary); 133 | file.write(buf, size); 134 | ``` 135 | 136 | ### Вспомогательные классы 137 | 138 | #### std::pair 139 | 140 | Тип позволяющий упаковать два значения в один объект. 141 | 142 | ```c++ 143 | #include 144 | 145 | auto p1 = std::pair(1, 2.0); 146 | auto p2 = std::make_pair(1, 2.0); 147 | 148 | auto x = p1.first; // int == 1 149 | auto y = p1.second; // double == 2 150 | ``` 151 | 152 | > pair имеет операторы сравнения позволяющие сделать лексикографическое сравнение элементов. 153 | 154 | #### std::tuple 155 | 156 | Тип позволяющий упаковать несколько значений в один объект. 157 | 158 | ```c++ 159 | #include 160 | 161 | auto t = std::make_tuple(1, 2.0, "abc"); 162 | int a = std::get<0>(t); 163 | double b = std::get<1>(t); 164 | std::string c = std::get<2>(t); 165 | ``` 166 | 167 | > Соответствие типов проверяется на этапе компиляции. 168 | 169 | > Как и pair имеет лексикографические операторы сравнения. 170 | 171 | #### std::tie 172 | 173 | tie, как и make_tuple создает tuple, но не объектов, а ссылок на них. 174 | 175 | ##### Использование tie для написания операторов сравнения 176 | 177 | ```c++ 178 | struct MyClass 179 | { 180 | int x_; 181 | std::string y_; 182 | double z_; 183 | 184 | bool operator<(const MyClass& o) const 185 | { 186 | return std::tie(x_, y_, z_) < std::tie(o.x_, o.y_, o.z_); 187 | } 188 | }; 189 | ``` 190 | 191 | ```c++ 192 | bool operator<(const MyClass& o) const 193 | { 194 | if (x_ != o.x_) 195 | return x_ < o.x_; 196 | if (y_ != o.y_) 197 | return y_ < o.y_; 198 | return z_ < o.z_; 199 | } 200 | ``` 201 | 202 | ### Библиотека шаблонов STL (Standard Template Library) 203 | 204 | 1. Контейнеры (containers) – хранение набора объектов в памяти 205 | 2. Итераторы (iterators) – средства для доступа к источнику данных (контейнер, поток) 206 | 3. Алгоритмы (algorithms) – типовые операции с данными 207 | 4. Адаптеры (adaptors) – обеспечение требуемого интерфейса 208 | 5. Функциональные объекты (functors) – функция как объект для использования другими компонентами 209 | 210 | ![](images/stl.png) 211 | 212 | ### O большое 213 | 214 | «О» большое – математическое обозначение для сравнения асимптотического поведения алгоритма. 215 | 216 | Фраза «сложность алгоритма есть O(f(n))» означает, что с ростом параметра n время работы алгоритма будет возрастать не быстрее, чем некоторая константа, умноженная на f(n). 217 | 218 | Типичные значения: 219 | 220 | 1. Время выполнения константно: O(1) 221 | 2. Линейное время: O(n) 222 | 3. Логарифмическое время: O(log n) 223 | 4. Время выполнения «n логарифмов n»: O(n log n) 224 | 5. Квадратичное время: O(n2) 225 | 226 | ### Контейнеры 227 | 228 | 1. Последовательные (Sequence containers) 229 | 2. Ассоциативные (Associative containers) 230 | 3. Неупорядоченные ассоциативные (Unordered associative containers) 231 | 4. Контейнеры-адаптеры (Container adaptors) 232 | 233 | ### Последовательные контейнеры 234 | 235 | #### std::array 236 | 237 | ```c++ 238 | #include 239 | 240 | template 241 | class array 242 | { 243 | T data_[N]; 244 | size_t size_; 245 | public: 246 | using size_type = size_t; 247 | using value_type = T; 248 | using reference = T&; 249 | using const_reference = const T&; 250 | 251 | constexpr size_type size() const noexcept 252 | { 253 | return size_; 254 | } 255 | 256 | constexpr bool empty() const noexcept 257 | { 258 | return false; 259 | } 260 | 261 | reference at(size_type pos) 262 | { 263 | if (size_ <= pos) 264 | throw std::out_of_range(std::to_string(pos)); 265 | return data_[pos]; 266 | } 267 | 268 | constexpr const_reference at(size_type pos) const; 269 | 270 | reference operator[](size_type pos) 271 | { 272 | return data_[pos]; 273 | } 274 | 275 | constexpr const_reference operator[](size_type pos) const; 276 | 277 | reference front() 278 | { 279 | return data_[0]; 280 | } 281 | 282 | constexpr const_reference front() const; 283 | 284 | reference back() 285 | { 286 | return data_[size_ - 1]; 287 | } 288 | 289 | constexpr const_reference back() const; 290 | 291 | T* data() noexcept 292 | { 293 | return data_; 294 | } 295 | 296 | const T* data() const noexcept; 297 | 298 | void swap(array& other); 299 | }; 300 | ``` 301 | 302 | | Вставка | Удаление | Поиск | Доступ | 303 | | ------- | -------- | ----- | ------ | 304 | | - | - | O(n) | O(1) | 305 | 306 | ```c++ 307 | std::array a = { 1, 2, 3, 4, 5 }; 308 | auto x = a[2]; 309 | a[2] = x * 2; 310 | ``` 311 | 312 | #### std::initializer_list 313 | 314 | ```c++ 315 | template 316 | class initializer_list 317 | { 318 | public: 319 | size_type size() const noexcept; 320 | const T* begin() const noexcept; 321 | const T* end() const noexcept; 322 | }; 323 | ``` 324 | 325 | ```c++ 326 | Array a = { 1, 2, 3 }; 327 | ``` 328 | 329 | ```c++ 330 | template 331 | class Array 332 | { 333 | public: 334 | Array(std::initializer_list init) 335 | { 336 | size_t i = 0; 337 | auto current = init.begin(); 338 | const auto end = init.end(); 339 | while (current != end) 340 | { 341 | data_[i++] = *current++; 342 | } 343 | } 344 | }; 345 | ``` 346 | 347 | #### std::vector 348 | 349 | ```c++ 350 | template> 352 | class vector 353 | { 354 | public: 355 | using size_type = size_t; 356 | using value_type = T; 357 | using reference = T&; 358 | using const_reference = const T&; 359 | using allocator_type = Alloc; 360 | 361 | explicit vector(size_type count); 362 | vector(size_type count, const value_type& defaultValue); 363 | vector(initializer_list init); 364 | 365 | iterator begin() noexcept; 366 | reverse_iterator rbegin() noexcept; 367 | const_iterator cbegin() const noexcept; 368 | const_reverse_iterator crbegin() const noexcept; 369 | 370 | iterator end() noexcept; 371 | reverse_iterator rend() noexcept; 372 | const_iterator cend() const noexcept; 373 | const_reverse_iterator crend() const noexcept; 374 | 375 | void push_back(value_type&& value); 376 | void push_back(const value_type& value); 377 | 378 | template 379 | void emplace_back(VT&&... values); 380 | 381 | iterator insert(const_iterator where, T&& value); 382 | iterator insert(const_iterator where, const T& value); 383 | 384 | template 385 | iterator emplace(const_iterator where, VT&&... values); 386 | 387 | void reserve(size_type count); // Выделяет память 388 | size_type capacity() const noexcept; 389 | 390 | void resize(size_type newSize); // Изменяет размер 391 | void resize(size_type newsize, const value_type& defaultValue); 392 | 393 | iterator erase(const_iterator where); 394 | 395 | // [from, to) 396 | iterator erase(const_iterator from, const_iterator to); 397 | 398 | void clear() noexcept; 399 | }; 400 | ``` 401 | 402 | ##### emplace_back vs push_back(&&) 403 | 404 | ```c++ 405 | class A 406 | { 407 | A(int, int) {} 408 | A(A&&) {} 409 | }; 410 | 411 | A a(1, 2); 412 | 413 | vec.push_back(std::move(a)); 414 | vec.emplace(1, 2); 415 | ``` 416 | 417 | Вектор - динамический массив, при добавлении элементов может изменять размер. 418 | 419 | | Вставка | Удаление | Поиск | Доступ | 420 | | --------------------- | ------------ | -------------------------- | ------ | 421 | | O(n) | O(n) | O(n) | O(1) | 422 | | В конце O(1) или O(n) | В конце O(1) | В отсортированном O(log n) | | 423 | 424 | #### Трюки с вектором 425 | 426 | ##### Быстрое удаление O(1) 427 | 428 | Если порядок элементов не важен, то меняем удаляемый элемент с последним местами и удаляем последний (pop_back). 429 | 430 | ##### Изменение размера вектора перед вставкой 431 | 432 | ```c++ 433 | const auto size = file.size(); 434 | std::vector data(size); 435 | for (size_t i = 0; i < size; ++i) 436 | data[i] = file.read(); 437 | ``` 438 | 439 | Позволяет сократить количество переаллокаций и существенно ускорить код. 440 | 441 | ##### Очистка вектора 442 | 443 | ```c++ 444 | std::vector data; 445 | for (int i = 0; i < 100500; ++i) 446 | data.push_back(i); 447 | data.clear(); 448 | std::cout << data.capacity() << std::endl; // >= 100500 449 | data.swap(std::vector()); 450 | std::cout << data.capacity() << std::endl; // 0 451 | ``` 452 | 453 | ```c++ 454 | data.shrink_to_fit(); // C++11 455 | ``` 456 | 457 | #### Итераторы (iterators) 458 | 459 | Объект предоставляющий доступ к элементам коллекции и осуществляющий навигацию по ним. 460 | 461 | Позволяет реализовать универсальные алгоритмы работы с контейнерами. 462 | 463 | Классификация итераторов: 464 | 465 | - Ввода (Input Iterator) 466 | - Однонаправленные (Forward Iterator) 467 | - Двунаправленные (Bidirectional Iterator) 468 | - Произвольного доступа (Random Access Iterator) 469 | - Вывода (Output Iterator) 470 | 471 | ![](images/iterators.gif) 472 | 473 | ```c++ 474 | template 475 | class Array 476 | { 477 | T data_[N]; 478 | }; 479 | ``` 480 | 481 | ```c++ 482 | template< 483 | typename _Category, 484 | typename _Tp, 485 | typename _Distance = ptrdiff_t, 486 | typename _Pointer = _Tp*, 487 | typename _Reference = _Tp&> 488 | struct iterator 489 | { 490 | /// One of the @link iterator_tags tag types@endlink. 491 | typedef _Category iterator_category; 492 | /// The type "pointed to" by the iterator. 493 | typedef _Tp value_type; 494 | /// Distance between iterators is represented as this type. 495 | typedef _Distance difference_type; 496 | /// This type represents a pointer-to-value_type. 497 | typedef _Pointer pointer; 498 | /// This type represents a reference-to-value_type. 499 | typedef _Reference reference; 500 | }; 501 | ``` 502 | 503 | ```c++ 504 | template 505 | class Iterator 506 | : public std::iterator 507 | { 508 | T* ptr_; 509 | public: 510 | explicit Iterator(T* ptr) 511 | : ptr_(ptr) 512 | { 513 | } 514 | 515 | bool operator==(const Iterator& other) const 516 | { 517 | return ptr_ == other.ptr_; 518 | } 519 | 520 | bool operator!=(const Iterator& other) const 521 | { 522 | return !(*this == other); 523 | } 524 | 525 | reference operator*() const 526 | { 527 | return *ptr_; 528 | } 529 | 530 | Iterator& operator++() 531 | { 532 | ++ptr_; 533 | return *this; 534 | } 535 | }; 536 | ``` 537 | 538 | > deprecated с С++17 539 | 540 | 541 | ```c++ 542 | template 543 | class Array 544 | { 545 | T data_[N]; 546 | public: 547 | using iterator = Iterator; 548 | 549 | iterator begin() noexcept 550 | { 551 | return iterator(data_); 552 | } 553 | 554 | iterator end() noexcept 555 | { 556 | return iterator(data_ + N); 557 | } 558 | }; 559 | ``` 560 | ```c++ 561 | Array arr; 562 | for (auto i : arr) 563 | std::cout << i; 564 | 565 | Array::iterator it = arr.begin(); 566 | while (it != arr.end()) 567 | ++it; 568 | ``` 569 | 570 | #### Адаптеры 571 | 572 | ```c++ 573 | #include 574 | ``` 575 | 576 | ##### reverse_iterator 577 | 578 | ```c++ 579 | template 580 | using reverse_iterator = reverse_iterator>; 581 | 582 | reverse_iterator rbegin() const noexcept 583 | { 584 | return reverse_iterator(end()); 585 | } 586 | 587 | reverse_iterator rend() const noexcept 588 | { 589 | return reverse_iterator(begin()); 590 | } 591 | ``` 592 | 593 | ##### back_insert_iterator 594 | 595 | Вставляет элемент в конец контейнера (push_back). 596 | 597 | ```c++ 598 | std::vector v; 599 | std::back_insert_iterator> it = std::back_inserter(v); 600 | *it = 5; 601 | ++it; 602 | *it = 7; 603 | // v == { 5, 7 } 604 | ``` 605 | 606 | ##### front_insert_iterator 607 | 608 | Вставляет элемент в начало контейнера (push_front). 609 | 610 | ##### insert_iterator 611 | 612 | Вставляет элемент в указанное место (insert). 613 | 614 | ```c++ 615 | std::set s; 616 | std::insert_iterator> it = std::inserter(s, s.end()); 617 | *s = 3; 618 | ``` 619 | 620 | #### Операции с итераторами 621 | 622 | ##### advance 623 | 624 | Переместить итератор на n 625 | 626 | ```c++ 627 | std::advance(it, 4); 628 | ``` 629 | 630 | ##### distance 631 | 632 | Расстояние между двумя итераторами 633 | 634 | ```c++ 635 | auto n = std::distance(it1, it2); 636 | ``` 637 | 638 | #### Потоковые итераторы 639 | 640 | Позволяют работать с потоком через интерфейс итератора. 641 | 642 | ##### ostream_iterator 643 | 644 | ```c++ 645 | auto it = std::ostream_iterator(std::cout, " "); 646 | *it = 3; 647 | ``` 648 | 649 | ##### istream_iterator 650 | 651 | ```c++ 652 | auto it = std::istream_iterator(std::cin); 653 | int x = *it; 654 | ``` 655 | 656 | ### Аллокаторы 657 | 658 | Назначение аллокатора - выделять и освобождать память. 659 | 660 | malloc и new - аллокаторы. 661 | 662 | ```c++ 663 | template> 665 | class vector 666 | { 667 | }; 668 | ``` 669 | 670 | ```c++ 671 | template 672 | class allocator 673 | { 674 | public: 675 | using value_type = T; 676 | using pointer = T*; 677 | using size_type = size_t; 678 | 679 | pointer allocate(size_type count); 680 | void deallocate(pointer ptr, size_type count); 681 | 682 | size_t max_size() const noexcept; 683 | }; 684 | ``` 685 | 686 | #### std::deque 687 | 688 | Интерфейс повторяет интерфейс std::vector, отличие в размещении в памяти - std::vector хранит данные в одном непрерывном куске памяти, std::deque хранит данные в связанных блоках по n элементов. 689 | 690 | ``` 691 | std::vector 692 | [ ][ ][ ][ ][ ][ ][ ] 693 | 694 | std::deque 695 | [ ][ ][ ] [ ][ ][ ] 696 | ``` 697 | 698 | | Вставка | Удаление | Поиск | Доступ | 699 | | --------------------- | --------------------- | -------------------------- | ------ | 700 | | O(n) | O(n) | O(n) | O(1) | 701 | | В конце и начале O(1) | В конце и начале O(1) | В отсортированном O(log n) | | 702 | 703 | #### std::forward_list 704 | 705 | Связный список, элементы которого храняться в произвольных участках памяти. 706 | 707 | ```c++ 708 | template 709 | struct Node 710 | { 711 | T value_; 712 | Node* next_; 713 | }; 714 | 715 | template 716 | class List 717 | { 718 | Node* root_; 719 | }; 720 | ``` 721 | ```c++ 722 | auto node = root_; 723 | while (node != nullptr) 724 | { 725 | node = node->next_; 726 | } 727 | ``` 728 | 729 | | Вставка | Удаление | Поиск | Доступ | 730 | | ------- | -------- | ----- | ------ | 731 | | O(1) | O(1) | O(n) | O(n) | 732 | 733 | Итератор списка не поддерживает произвольный доступ, следовательно алгоритмы STL, которые требуют random access iterator работать со списком не будут, например, std::sort 734 | 735 | ##### Нахождение петли в списке 736 | 737 | Берем 2 итератора. Первый увеличиваем каждую итерацию на 1, второй на 2. Если итераторы на какой-либо итерации встретились - петля есть, если дошли до конца - петли нет. 738 | 739 | #### std::list 740 | 741 | Отличие от односвязного списка - возможность перемещаться в обратном направлении. 742 | 743 | ```c++ 744 | template 745 | struct Node 746 | { 747 | T value_; 748 | Node* prev_; 749 | Node* next_; 750 | }; 751 | ``` 752 | 753 | ##### Разворот списка 754 | 755 | Идем по списку и меняем местами значения prev и next. 756 | 757 | ### Ассоциативные контейнеры 758 | 759 | Контейнер позволяющий хранить пары вида (ключ, значение) и поддерживающий операции добавления пары, а также поиска и удаления пары по ключу. 760 | 761 | Элементы отсортированы по ключу: 762 | 763 | - ```set``` 764 | - ```map``` 765 | - ```multiset``` 766 | - ```multimap``` 767 | 768 | Элементы не отсортированы: 769 | 770 | - ```unordered_set``` 771 | - ```unordered_map``` 772 | - ```unordered_multiset``` 773 | - ```unordered_multimap``` 774 | 775 | set будем представлять как вырожденный случай map, где ключ равен значению. 776 | 777 | В set и map ключи уникальны, в multi версиях контейнеров допускаются наличие значений с одинаковым ключом. 778 | 779 | | | Вставка | Удаление | Поиск | Доступ | 780 | | ---------------------------- | ------------- | ------------- | ------------- | ------------- | 781 | | set, map | O(log n) | O(log n) | O(log n) | O(log n) | 782 | | unordered_set, unordered_map | O(1) или O(n) | O(1) или O(n) | O(1) или O(n) | O(1) или O(n) | 783 | 784 | ```c++ 785 | #include 786 | 787 | std::unordered_map frequencyDictionary; 788 | 789 | std::string word; 790 | while (getWord(word)) 791 | { 792 | auto it = frequencyDictionary.find(word); 793 | if (it == frequencyDictionary.end()) 794 | frequencyDictionary[word] = 1; 795 | else 796 | it->second++; 797 | } 798 | ``` 799 | 800 | ### Контейнеры-адаптеры 801 | 802 | Являются обертками над другими контейнерами и предоставляют нужный интерфейс. 803 | 804 | - ```stack>``` 805 | - ```queue``` 806 | - ```priority_queue``` 807 | 808 | #### std::stack 809 | 810 | Реализует интерфейс стека - положить значение в стек, извлечь значение из стека, последний пришел первый вышел (LIFO). 811 | 812 | ```c++ 813 | #include 814 | 815 | std::stack s; 816 | s.push(3); 817 | s.push(5); 818 | int x = s.top(); // 5 819 | s.pop(); 820 | int y = s.top(); // 3 821 | ``` 822 | 823 | ```c++ 824 | template > 826 | class stack 827 | { 828 | Container data_; 829 | public: 830 | using value_type = T; 831 | using size_type = typename Container::size_type; 832 | using reference = T&; 833 | using const_reference = const T&; 834 | 835 | void push(value_type&& value) 836 | { 837 | data_.push_back(std::move(value)); 838 | } 839 | 840 | void push(const value_type& value) 841 | { 842 | data_.push_back(value); 843 | } 844 | 845 | template 846 | void emplace(VT&&... values) 847 | { 848 | data_.emplace_back(std::forward(values)...); 849 | } 850 | 851 | bool empty() const 852 | { 853 | return data_.empty(); 854 | } 855 | 856 | size_type size() const 857 | { 858 | return data_.size(); 859 | } 860 | 861 | reference top() 862 | { 863 | return data_.back(); 864 | } 865 | 866 | const_reference top() const 867 | { 868 | return data_.back(); 869 | } 870 | 871 | void pop() 872 | { 873 | data_.pop_back(); 874 | } 875 | }; 876 | ``` 877 | 878 | #### std::queue 879 | 880 | Реализует интерфейс очереди - положить значение в стек, извлечь первое значение из стека, первый пришел первый вышел (FIFO). 881 | 882 | ```c++ 883 | #include 884 | 885 | template< 886 | class T, 887 | class Container = std::deque> 888 | class queue; 889 | ``` 890 | ```c++ 891 | void push(const value_type& value); 892 | void push(value_type&& value); 893 | ``` 894 | ```c++ 895 | reference front(); 896 | const_reference front() const; 897 | ``` 898 | ```c++ 899 | void pop(); 900 | ``` 901 | 902 | #### std::priority_queue 903 | 904 | Отличие от queue - за O(1) можно извлечь элемент наиболее полно удовлетворяющий условию. 905 | 906 | ```c++ 907 | #include 908 | 909 | template< 910 | class T, 911 | class Container = std::vector, 912 | class Compare = std::less> 913 | class priority_queue; 914 | ``` 915 | 916 | ```c++ 917 | struct Packet 918 | { 919 | int priority_; 920 | std::string payload_; 921 | }; 922 | 923 | auto PriorityComparator = 924 | [](const Packet& x, const Packet& y) 925 | { 926 | return x.priority_ > y.priority_; 927 | }; 928 | 929 | using PacketQueue = std::priority_queue 930 | < 931 | Packet, 932 | std::vector, 933 | decltype(PriorityComparator) 934 | >; 935 | 936 | PacketQueue incoming(PriorityComparator); 937 | ``` 938 | 939 | #### std::string 940 | 941 | ```c++ 942 | template< 943 | class CharT, 944 | class Traits = std::char_traits, 945 | class Allocator = std::allocator 946 | > class basic_string; 947 | ``` 948 | 949 | ```c++ 950 | using string = std::basic_string; 951 | ``` 952 | 953 | ```c++ 954 | class string 955 | { 956 | char* data_; 957 | size_t size_; 958 | size_t capacity_; 959 | }; 960 | ``` 961 | 962 | > sizeof == 24 963 | 964 | ##### SSO (Small String Optimization) 965 | 966 | ```c++ 967 | class string 968 | { 969 | size_t size_; 970 | 971 | struct Long 972 | { 973 | size_t capacity_; 974 | char* data_; 975 | }; 976 | 977 | struct Short 978 | { 979 | char data_[sizeof(Long)]; 980 | }; 981 | 982 | union 983 | { 984 | Long l_; 985 | Short s_; 986 | } content_; 987 | 988 | const char* c_str() const 989 | { 990 | if (size_ < sizeof(Long)) 991 | return content_.s_.data_; 992 | else 993 | return content_.l_.data_; 994 | } 995 | }; 996 | ``` 997 | 998 | ### Библиотека алгоритмов STL 999 | 1000 | 1. Не изменяющие последовательные алгоритмы 1001 | 2. Изменяющие последовательные алгоритмы 1002 | 3. Алгоритмы сортировки 1003 | 4. Бинарные алгоритмы поиска 1004 | 5. Алгоритмы слияния 1005 | 6. Кучи 1006 | 7. Операции отношений 1007 | 1008 | ```c++ 1009 | #include 1010 | ``` 1011 | 1012 | #### Не изменяющие последовательные алгоритмы 1013 | 1014 | Не изменяют содержимое последовательности и решают задачи поиска, подсчета элементов, установления равенства последовательностей. 1015 | 1016 | ##### adjacent_find 1017 | 1018 | Возвращает итератор, указывающий на первую пару одинаковых объектов, если такой пары нет, то итератор - end. 1019 | 1020 | ```c++ 1021 | std::vector v { 1, 2, 3, 3, 4 }; 1022 | auto i = std::adjacent_find(v.begin(), v.end()); 1023 | // *i == 3 1024 | ``` 1025 | 1026 | ##### all_of 1027 | 1028 | Проверяет, что все элементы последовательности удовлетворяют предикату. 1029 | 1030 | ```c++ 1031 | std::vector v { 1, 2, 3, 4 }; 1032 | if (std::all_of(v.begin(), v.end(), [](int x) { return x < 5; })) 1033 | std::cout << "all elements are less than 5"; 1034 | ``` 1035 | 1036 | ##### any_of 1037 | 1038 | Проверяет, что хоть один элемент последовательности удовлетворяет предикату. 1039 | 1040 | ##### none_of 1041 | 1042 | Проверяет, что все элементы последовательности не удовлетворяют предикату. 1043 | 1044 | ##### count, count_if 1045 | 1046 | Возвращает количество элементов, значение которых равно value или удовлетворяет предикату. 1047 | 1048 | ```c++ 1049 | std::vector v { 3, 2, 3, 4 }; 1050 | auto n = std::count(v.begin(), v.end(), 3); 1051 | // n == 2 1052 | ``` 1053 | 1054 | ##### equal 1055 | 1056 | Проверяет, что две последовательности идентичны. 1057 | 1058 | ```c++ 1059 | bool isPalindrome(const std::string& s) 1060 | { 1061 | auto middle = s.begin() + s.size() / 2; 1062 | return std::equal(s.begin(), mid, s.rbegin()); 1063 | } 1064 | 1065 | isPalindrome("level"); // true 1066 | ``` 1067 | 1068 | Есть версия принимающая предикат. 1069 | 1070 | ##### find, find_if, find_if_not 1071 | 1072 | Находит первый элемент последовательности удовлетворяющий условию. 1073 | 1074 | ##### find_end 1075 | 1076 | Находит последний элемент последовательности удовлетворяющий условию. 1077 | 1078 | ##### find_first_of 1079 | 1080 | Ищет в первой последовательности первое вхождение любого элемента из второй последовательности. 1081 | 1082 | ```c++ 1083 | std::vector v { 0, 2, 3, 25, 5 }; 1084 | std::vector t { 3, 19, 10, 2 }; 1085 | 1086 | auto result = std::find_first_of( 1087 | v.begin(), v.end(), 1088 | t.begin(), t.end()); 1089 | 1090 | if (result == v.end()) 1091 | std::cout << "no matches found\n"; 1092 | else 1093 | std::cout << "found a match at " 1094 | << std::distance(v.begin(), result) << "\n"; 1095 | } 1096 | 1097 | // found a match at 1 1098 | ``` 1099 | 1100 | ##### for_each 1101 | 1102 | Вызывает функцию с каждым элементом последовательности. 1103 | 1104 | ```c++ 1105 | std::vector v { 3, 2, 3, 4 }; 1106 | auto print = [](int x) { std::cout << x; }; 1107 | std::for_each(v.begin(), v.end(), print); 1108 | ``` 1109 | 1110 | ##### search 1111 | 1112 | Ищет вхождение одной последовательности в другую последовательность. 1113 | 1114 | ##### search_n 1115 | 1116 | Возвращает итератор на начало последовательности из n одинкаовых элементов или end. 1117 | 1118 | ```c++ 1119 | auto it = search_n(data.begin(), data.end(), howMany, value); 1120 | ``` 1121 | 1122 | ##### mismatch 1123 | 1124 | Возвращает пару итераторов на первое несовпадение элементов двух последовательностей. 1125 | 1126 | ```c++ 1127 | std::vector x { 1, 2 }; 1128 | std::vector y { 1, 2, 3, 4 }; 1129 | auto pair = std::mismatch(x.begin(), x.end(), y.begin()); 1130 | // pair.first == x.end() 1131 | // pair.second = y.begin() + 2 1132 | ``` 1133 | 1134 | #### Модифицирующие последовательные алгоритмы 1135 | 1136 | Изменяют содержимое последовательности, решают задачи копирования, замены, удаления, перестановки значений и т.д. 1137 | 1138 | ##### copy, copy_if, copy_n 1139 | 1140 | Копируют диапазон последовательности в новое место. 1141 | 1142 | ```c++ 1143 | std::vector data { 1, 2, 3, 4 }; 1144 | std::copy(data.begin(), data.end(), 1145 | std::ostream_iterator(std::cout, " ")); 1146 | ``` 1147 | 1148 | ```c++ 1149 | std::vector data { 1, 2, 3, 4 }; 1150 | std::vector out; 1151 | std::copy(data.begin(), data.end(), std::back_inserter(out)); 1152 | ``` 1153 | 1154 | ```c++ 1155 | char* source = ...; 1156 | size_t size = 1024; 1157 | char* destination = ...; 1158 | std::copy(source, source + size, destination); 1159 | ``` 1160 | 1161 | ##### copy_backward 1162 | 1163 | Аналогично copy, но в обратном порядке. 1164 | 1165 | ##### move, move_backward 1166 | 1167 | Аналогично copy, но вместо копирования диапазона используется перемещение. 1168 | 1169 | ##### fill, fill_n 1170 | 1171 | Заполнение диапазона значениями. 1172 | 1173 | ```c++ 1174 | std::vector data { 1, 2, 3, 4 }; 1175 | std::fill(data.begin(), data.end(), 0); 1176 | ``` 1177 | 1178 | ##### generate, generate_n 1179 | 1180 | Заполнение сгенерированными значениями. 1181 | 1182 | ```c++ 1183 | std::vector randomNumbers; 1184 | auto iter = std::back_inserter(randomNumbers); 1185 | std::generate_n(iter, 100, std::rand); 1186 | ``` 1187 | 1188 | ##### remove, remove_if 1189 | 1190 | Удаляет элементы удовлетворяющие критерию. Если быть точным данные алгоритмы ничего не удаляют, просто изменяют последовательность так, чтобы удаляемые элементы были в конце и возвращают итератор на первый элемент. 1191 | 1192 | ```c++ 1193 | std::string str = "Text\t with\t \ttabs"; 1194 | auto from = std::remove_if( 1195 | str.begin(), str.end(), 1196 | [](char x) { return x == '\t'; }) 1197 | // Text with tabs\t\t\t 1198 | str.erase(from, str.end()); 1199 | // Text with tabs 1200 | ``` 1201 | 1202 | ##### remove_copy, remove_copy_if 1203 | 1204 | То же, что и remove, но то, что не должно удаляться копируется в новое место. 1205 | 1206 | ```c++ 1207 | std::string str = "Text with spaces"; 1208 | std::remove_copy(str.begin(), str.end(), 1209 | std::ostream_iterator(std::cout), ' '); 1210 | ``` 1211 | 1212 | ``` 1213 | Textwithspaces 1214 | ``` 1215 | 1216 | ##### replace, replace_if 1217 | 1218 | Заменяет элементы удовлетворяющие условию в последовательности. 1219 | 1220 | ```c++ 1221 | std::string str = "Text\twith\ttabs"; 1222 | std::replace_if(str.begin(), str.end(), 1223 | [](char x) { return x == '\t'; }, ' '); 1224 | ``` 1225 | 1226 | ##### reverse 1227 | 1228 | Поворачивает элементы последовательности задом наперед. 1229 | 1230 | ##### swap 1231 | 1232 | Меняет два элемента местами. 1233 | 1234 | ```c++ 1235 | int x = 3; 1236 | int y = 5; 1237 | std::swap(x, y); 1238 | ``` 1239 | 1240 | ##### iter_swap 1241 | 1242 | Меняет два элемента на которые указывают итераторы местами. 1243 | 1244 | ##### swap_ranges 1245 | 1246 | Меняет местами два диапазона последовательностей. 1247 | 1248 | ##### shuffle 1249 | 1250 | Перемешивает диапазон последовательности. 1251 | 1252 | ```c++ 1253 | std::vector v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 1254 | 1255 | std::random_device rd; 1256 | std::mt19937 gen(rd()); 1257 | 1258 | std::shuffle(v.begin(), v.end(), gen); 1259 | ``` 1260 | 1261 | ##### unique 1262 | 1263 | Удаляет (аналогично remove) дубликаты в последовательности, последовательность должна быть отсортирована. 1264 | 1265 | ```c++ 1266 | std::vector v { 1, 1, 2, 3, 3 }; 1267 | const auto from = std::unique(v.begin(), v.end()); 1268 | // 1 2 3 1 3 1269 | v.erase(from, v.end()); 1270 | // 1 2 3 1271 | ``` 1272 | 1273 | #### Алгоритмы сортировки 1274 | 1275 | ##### is_sorted 1276 | 1277 | Проверяет упорядочена ли последовательность. 1278 | 1279 | ```c++ 1280 | std::vector v = { 1, 2, 3 }; 1281 | const bool isSoreted = 1282 | std::is_sorted(v.begin(), v.end()); 1283 | // true 1284 | ``` 1285 | 1286 | ##### sort 1287 | 1288 | Сортирует последовательность. 1289 | 1290 | ```c++ 1291 | std::vector v = { 2, 3, 1 }; 1292 | std::sort(v.begin(), v.end(), 1293 | [](int x, int y) { return x > y; }); 1294 | // 3 2 1 1295 | ``` 1296 | 1297 | > Сложность O(n * log n) 1298 | 1299 | ##### partial_sort 1300 | 1301 | Сортирует часть последовательности (TOP-N). 1302 | 1303 | ```c++ 1304 | std::array s { 5, 7, 4, 2, 8, 6, 1, 9, 0, 3 }; 1305 | std::partial_sort(s.begin(), s.begin() + 3, s.end()); 1306 | ``` 1307 | 1308 | ``` 1309 | 0 1 2 7 8 6 5 9 4 3 1310 | ``` 1311 | 1312 | > Сложность O((last-first) * log (middle-first)) 1313 | 1314 | ##### stable_sort 1315 | 1316 | Сортирует последовательность, если два объекта равны, их порядок не изменится. 1317 | 1318 | > Сложность O(n * log2 n) 1319 | 1320 | ##### nth_element 1321 | 1322 | Помещает элемент в позицию n, которую он занимал бы после сортировки всего диапазона. 1323 | 1324 | ```c++ 1325 | std::vector v { 3, 1, 4, 5, 2 }; 1326 | const auto medianIndex = v.size() / 2; 1327 | std::nth_element(v.begin(), v.begin() + medianIndex, v.end()); 1328 | const auto median = v[medianIndex]; 1329 | // 3 1330 | ``` 1331 | 1332 | > Сложность O(n) 1333 | 1334 | #### Алгоритмы бинарного поиска 1335 | 1336 | Последовательности к которым применяются алгоритмы должны быть отсортированы. 1337 | 1338 | ##### binary_search 1339 | 1340 | Поиск по отсортированной последовательности. 1341 | 1342 | ```c++ 1343 | std::vector v { 1, 2, 3, 4, 5 }; 1344 | bool has2 = std::binary_search(v.begin(), v.end(), 2); 1345 | // true 1346 | ``` 1347 | 1348 | ##### lower_bound 1349 | 1350 | Возвращает итератор, указывающий на первый элемент, который не меньше, чем value. 1351 | 1352 | ```c++ 1353 | std::vector v { 1, 2, 3, 4, 5 }; 1354 | // ^ 1355 | auto it = std::lower_bound(v.begin(), v.end(), 2); 1356 | ``` 1357 | 1358 | ##### upper_bound 1359 | 1360 | Возвращает итератор, указывающий на первый элемент, который больше, чем value. 1361 | 1362 | ```c++ 1363 | std::vector v { 1, 2, 3, 4, 5 }; 1364 | // ^ 1365 | auto it = std::upper_bound(v.begin(), v.end(), 2); 1366 | ``` 1367 | 1368 | ##### equal_range 1369 | 1370 | Возвращает такую пару итераторов, что элемент на который указывает первый итератор не меньше value, а элемент на который указывает второй итератор больше value. 1371 | 1372 | ```c++ 1373 | std::vector v { 1, 2, 3, 4, 5 }; 1374 | // ^ ^ 1375 | auto pair = std::equal_range(v.begin(), v.end(), 2); 1376 | ``` 1377 | 1378 | ### Практическая работа 1379 | 1380 | Написать свой контейнер Vector аналогичный std::vector, аллокатор и итератор произвольного доступа для него. Из поддерживаемых методов достаточно operator[], push_back, pop_back, empty, size, clear, begin, end, rbegin, rend, resize, reserve. 1381 | 1382 | Интерфейс аллокатора и итератора смотрите в документации. 1383 | 1384 | ### Дополнительно 1385 | 1386 | Примера итератора, который итерирует последовательность по четным числам 1387 | 1388 | ```c++ 1389 | #include 1390 | #include 1391 | #include 1392 | 1393 | template 1394 | class OddIterator 1395 | : public std::iterator 1396 | { 1397 | Iter current_; 1398 | Iter end_; 1399 | 1400 | void findNext() 1401 | { 1402 | while (current_ != end_) 1403 | { 1404 | if (*current_ % 2 == 0) 1405 | return; 1406 | ++current_; 1407 | } 1408 | } 1409 | 1410 | public: 1411 | OddIterator(Iter&& begin, Iter&& end) 1412 | : current_(std::move(begin)) 1413 | , end_(std::move(end)) 1414 | { 1415 | findNext(); 1416 | } 1417 | 1418 | bool operator==(const OddIterator& other) const 1419 | { 1420 | return current_ == other.current_; 1421 | } 1422 | 1423 | bool operator!=(const OddIterator& other) const 1424 | { 1425 | return !(*this == other); 1426 | } 1427 | 1428 | void operator++() 1429 | { 1430 | if (current_ != end_) 1431 | { 1432 | ++current_; 1433 | findNext(); 1434 | } 1435 | } 1436 | 1437 | int operator*() const 1438 | { 1439 | return *current_; 1440 | } 1441 | }; 1442 | 1443 | template 1444 | OddIterator getBegin(const Container& data) 1445 | { 1446 | return OddIterator(data.cbegin(), data.cend()); 1447 | } 1448 | 1449 | template 1450 | OddIterator getEnd(const Container& data) 1451 | { 1452 | return OddIterator(data.cend(), data.cend()); 1453 | } 1454 | 1455 | int main() 1456 | { 1457 | std::vector data1 = { 9, 8, 1, 3, 4, 5, 6 }; 1458 | std::for_each(getBegin(data1), getEnd(data1), [](int x) { std::cout << x << '\n'; }); 1459 | 1460 | std::cout << '\n'; 1461 | 1462 | std::set data2(data1.begin(), data1.end()); 1463 | std::for_each(getBegin(data2), getEnd(data2), [](int x) { std::cout << x << '\n'; }); 1464 | 1465 | return 0; 1466 | } 1467 | ``` 1468 | 1469 | EOF 1470 | --------------------------------------------------------------------------------