diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 13fd6e0a..32b897e4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -1,6 +1,6 @@ name: Testing Tasks -on: [push, pull_request] +on: [push] jobs: test: @@ -30,12 +30,17 @@ jobs: tasks=("addition" "rms" "print_bits" "check_flags" "length_lit" "quadratic" "char_changer" "swap_ptr" "func_array" "longest" "last_of_us" "little_big" "pretty_array" "data_stats" "unique" "range" "minmax" "find_all" "os_overload" "easy_compare" "filter" "enum_operators" - "stack" "queue" "ring_buffer" "phasor") + "stack" "queue" "ring_buffer" "phasor" + "tracer" "string_view" "cow_string" "simple_vector") declare -i passed_count=0 declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for changed tasks ===" for task in "${tasks[@]}"; do @@ -49,13 +54,16 @@ jobs: if ./build/tasks/test_$task; then echo "✅ test_$task PASSED" passed_count+=1 + passed_tasks+=("$task") else echo "❌ test_$task FAILED" failed_count+=1 + failed_tasks+=("$task") fi else echo "❌ test_$task build FAILED" failed_count+=1 + failed_tasks+=("$task") fi fi done @@ -63,8 +71,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_01.yml b/.github/workflows/testing_week_01.yml index 3ece11af..be25e996 100644 --- a/.github/workflows/testing_week_01.yml +++ b/.github/workflows/testing_week_01.yml @@ -18,6 +18,9 @@ on: - quadratic - char_changer + schedule: + - cron: '59 20 4 12 *' # UTC: 20:59 = 23:59 MSK 4 December + jobs: test: runs-on: ubuntu-latest @@ -33,7 +36,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 01_week/tasks/*/; do @@ -48,6 +52,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -55,6 +61,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -80,8 +90,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_02.yml b/.github/workflows/testing_week_02.yml index dc37cec9..f4f63b94 100644 --- a/.github/workflows/testing_week_02.yml +++ b/.github/workflows/testing_week_02.yml @@ -17,6 +17,9 @@ on: - little_big - pretty_array + schedule: + - cron: '59 20 11 12 *' # UTC: 20:59 = 23:59 MSK 11 December + jobs: test: runs-on: ubuntu-latest @@ -32,7 +35,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 02_week/tasks/*/; do @@ -47,6 +51,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -54,6 +60,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -79,8 +89,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_03.yml b/.github/workflows/testing_week_03.yml index fdb0892f..38993cfa 100644 --- a/.github/workflows/testing_week_03.yml +++ b/.github/workflows/testing_week_03.yml @@ -38,7 +38,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 03_week/tasks/*/; do @@ -53,6 +54,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -60,6 +63,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -85,8 +92,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_04.yml b/.github/workflows/testing_week_04.yml index 9f93eaed..e27e55bb 100644 --- a/.github/workflows/testing_week_04.yml +++ b/.github/workflows/testing_week_04.yml @@ -33,7 +33,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 04_week/tasks/*/; do @@ -48,6 +49,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -55,6 +58,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -80,8 +87,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_05.yml b/.github/workflows/testing_week_05.yml new file mode 100644 index 00000000..312f5c25 --- /dev/null +++ b/.github/workflows/testing_week_05.yml @@ -0,0 +1,105 @@ +name: Testing Tasks Week 05 + +on: + workflow_dispatch: + inputs: + tasks: + description: 'Select tasks to test' + required: true + type: choice + default: 'all' + options: + - all + - tracer + - string_view + - cow_string + - simple_vector + + schedule: + - cron: '59 20 16 02 *' # UTC: 20:59 = 23:59 MSK 16 February + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and CMake + run: sudo apt install -y cmake build-essential g++-14 libgtest-dev libgmock-dev + + - name: Configure project + run: cmake -B build + + - name: Determine tasks to run + id: get-tasks + run: | + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then + # Find all tasks + TASKS=() + for dir in 05_week/tasks/*/; do + task=$(basename "$dir") + TASKS+=("$task") + done + echo "tasks=${TASKS[*]}" >> $GITHUB_OUTPUT + else + # Используем указанную задачу + echo "tasks=${{ github.event.inputs.tasks }}" >> $GITHUB_OUTPUT + fi + + - name: Build and run tests for selected tasks + run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" + + IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" + + declare -i passed_count=0 + declare -i failed_count=0 + declare -i task_count=0 + + # task name arrays + passed_tasks=() + failed_tasks=() + + echo "=== Starting tests for selected tasks ===" + + for task in "${tasks[@]}"; do + task_count+=1 + echo "=== Processing $task ===" + + if cmake --build build --target test_$task; then + echo "✅ test_$task built successfully" + + if ./build/tasks/test_$task; then + echo "✅ test_$task PASSED" + passed_count+=1 + passed_tasks+=("$task") + else + echo "❌ test_$task FAILED" + failed_count+=1 + failed_tasks+=("$task") + fi + else + echo "❌ test_$task build FAILED" + failed_count+=1 + failed_tasks+=("$task") + fi + done + + echo "=== Test Results Summary ===" + echo "Total tasks in list: ${#tasks[@]}" + echo "Processed: $task_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" + + if [ $failed_count -gt 0 ]; then + echo "❌ Some tasks failed!" + exit 1 + elif [ $task_count -eq 0 ]; then + echo "No tasks were processed (no changes)" + exit 0 + else + echo "✅ All processed tasks passed!" + exit 0 + fi \ No newline at end of file diff --git a/01_week/tasks/addition/addition.cpp b/01_week/tasks/addition/addition.cpp index 92872802..3ca160a1 100644 --- a/01_week/tasks/addition/addition.cpp +++ b/01_week/tasks/addition/addition.cpp @@ -1,7 +1,10 @@ -#include -#include - +#include // заголовочный файл с целочисленными типами фиксированного размера +#include // из этого мы ничего не используем +// функция принимает две перменных типа int и возвращает число типа int64_t int64_t Addition(int a, int b) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file + + // Чтобы сложение выполнялось в int64_t, приводим a и b к этому типу +// static_cast<новый тип>(переменная) + return static_cast(a) + static_cast(b); +} diff --git a/01_week/tasks/char_changer/char_changer.cpp b/01_week/tasks/char_changer/char_changer.cpp index 3a7344d9..6cec0357 100644 --- a/01_week/tasks/char_changer/char_changer.cpp +++ b/01_week/tasks/char_changer/char_changer.cpp @@ -1,7 +1,88 @@ #include -#include - +#include // применял isspace, isdigit, isupper, islower, toupper size_t CharChanger(char array[], size_t size, char delimiter = ' ') { - throw std::runtime_error{"Not implemented"}; + if (size == 0) { + return 0; + } + + size_t read = 0; // это индекс для чтения + size_t write = 0; // это индекс для запси символов + + // идем по массиву пока не выйдем за него + // и пока текущий символ не конец строки, конец строки по условию это '\0' + while (read < size && array[read] != '\0') { + // в cppreference указано, что isspace должно принимать безнаковый символ, + // поэтому преобразуем текущий символ из char в unsigned char + unsigned char uc = static_cast(array[read]); + // если текущий символ пробельный + if (std::isspace(uc)) { + + // пропускаем все подряд идущие пробельные символы + while (read < size && array[read] != '\0') { + unsigned char c = static_cast(array[read]); + if (!std::isspace(c)) { + break; // текущий символ больше не пробел — выходим + } + ++read; // пробел — пропускаем и идём дальше + } + // теперь когда мы прочитали все пробелы записывыем в write только один пробел + array[write++] = delimiter; + + } else { + // Теперь рассматриваем случай когда у нас идут подряд одинаковые символы + // Текущий символ массива + char current = array[read]; + size_t count = 0; // это как счетчик, то есть сколько повторябщихся символов + // идем пока текущий символ не превзойдет размер массива и + // символ не конец строки и символ на текущей позиции такой же как и currentт + while (read < size && array[read] != '\0' && array[read] == current) { + ++count; + ++read; + } + + // Определяем, какой символ писать по правилам + // в cppreference указано, что isdigit,isupper,islower должно принимать безнаковый символ, + // поэтому преобразуем текущий символ current из char в unsigned char + unsigned char cu = static_cast(current); + char Char; + + if (std::isdigit(cu)) { // цифры заменяем на '*' + Char = '*'; + } else if (std::isupper(cu)) { //прописные латинские не меняем + Char = current; + } else if (std::islower(cu)) { // строчные на прописные + Char = static_cast(std::toupper(cu)); + } else { // остальное на '_' + Char = '_'; + } + + // записываем символ в write + array[write++] = Char; + + // пишем количество повторений, если count > 1 + if (count > 1) { + if (count >= 10) { // Если повторений не менее 10, указывается `0` после символа + array[write++] = '0'; + } else { + // записываем в write число посторений (то есть елси 2222, то 4), + // Например, если было "2222", то count = 4, и мы должны записать символ '4'. + // В кодировке ASCII код символа '0' равен 48 + // Поэтому, чтобы получить символ '4', берём код '0' (48) и прибавляем 4 (48 + 4 = 52 — это код '4') + array[write++] = static_cast('0' + count); // + } + } + } + } + + // завершаем строку + if (write >= size) { + write = size - 1; + } + array[write] = '\0'; + + return write; } + + + diff --git a/01_week/tasks/check_flags/check_flags.cpp b/01_week/tasks/check_flags/check_flags.cpp index 75e7c652..665139dd 100644 --- a/01_week/tasks/check_flags/check_flags.cpp +++ b/01_week/tasks/check_flags/check_flags.cpp @@ -1,8 +1,8 @@ -#include -#include + #include + #include - -enum class CheckFlags : uint8_t { + // каждый флаг это один бит в маске, флаг занимает ровно один + enum class CheckFlags : uint8_t { NONE = 0, TIME = (1 << 0), DATE = (1 << 1), @@ -12,7 +12,82 @@ enum class CheckFlags : uint8_t { DEST = (1 << 5), ALL = TIME | DATE | USER | CERT | KEYS | DEST }; + void PrintCheckFlags(CheckFlags flags) { + // мы не можем напрямую работать с flags, поэтому преобразуем + // flags из типа CheckFlags в обычное число типа uint8_t + // uint8_t потому что все флаги храняться в одном байте + uint8_t mask = static_cast(flags); + // так как маска = 8 бит, то и все разрешенные флаги тоже 8 бит (поэтому uint8_t) + // но в целом как для mask, так и для allowedFlags могли написать int и не париться, но с uint8_t корректнее + uint8_t allowedFlags = static_cast(CheckFlags::ALL); + + // Если передано значение выходит из возможного диапазона значений, то вывод следует оставить пустым. + // к примеру если мы на вход подаем значение 128, а 128 в двочиной это 10000000 (mask), allowedFlags = 01111111, то + // 10000000 + // 11000000 (инверсия) + // -------- + // 10000000 (такого флага в маске нет) + if (mask & ~allowedFlags) { + return; + } + + // Если передан флаг отсутствия проверок, то выводим пустые `[]` + if (mask == 0) { + std::cout << "[]"; + return; + } + + // дальше расматриваем все возможные случаи проверок + std::cout << "["; + // флаг состояний для запятой + bool first = true; + + if (mask & static_cast(CheckFlags::TIME)) { + if (!first) { + std::cout << ","; + } + std::cout << "TIME"; + first = false; + } + + if (mask & static_cast(CheckFlags::DATE)) { + if (!first) { + std::cout << ","; + } + std::cout << "DATE"; + first = false; + } + + if (mask & static_cast(CheckFlags::USER)) { + if (!first) { + std::cout << ","; + } + std::cout << "USER"; + first = false; + } + + if (mask & static_cast(CheckFlags::CERT)) { + if (!first) { + std::cout << ","; + } + std::cout << "CERT"; + first = false; + } + + if (mask & static_cast(CheckFlags::KEYS)) { + if (!first) { + std::cout << ","; + } + std::cout << "KEYS"; + first = false; + } + + if (mask & static_cast(CheckFlags::DEST)) { + if (!first) { + std::cout << ","; + } + std::cout << "DEST"; + } -void PrintCheckFlags(CheckFlags flags) { - throw std::runtime_error{"Not implemented"}; + std::cout << "]"; } diff --git a/01_week/tasks/length_lit/length_lit.cpp b/01_week/tasks/length_lit/length_lit.cpp index e69de29b..d90ec9f8 100644 --- a/01_week/tasks/length_lit/length_lit.cpp +++ b/01_week/tasks/length_lit/length_lit.cpp @@ -0,0 +1,62 @@ +// в метрах +const double INCH = 0.0254; +const double FOOT = 0.3048; +const double CM = 0.01; + +// перевод из дюймов в метры +double operator"" _in_to_m(long double v) { + return v * INCH; +} +// перевод из дюймов в сантиметры +double operator"" _in_to_cm(long double v) { + double m = v * INCH; + return m / CM; +} +// перевод из дюймов в футы +double operator"" _in_to_ft(long double v) { + double m = v * INCH; + return m / FOOT; +} + +// перевод из футов в метры +double operator"" _ft_to_m(long double v) { + return v * FOOT; +} +// перевод из футов в сантиметры +double operator"" _ft_to_cm(long double v) { + double m = v * FOOT; + return m / CM; +} +// перевод из футов в дюймы +double operator"" _ft_to_in(long double v) { + double m = v * FOOT; + return m / INCH; +} + +// перевод из сантиметров в метры +double operator"" _cm_to_m(long double v) { + return v * CM; +} +// перевод из сантиметров в дюймы +double operator"" _cm_to_in(long double v) { + double m = v * CM; + return m / INCH; +} +// перевод из сантиметров в футы +double operator"" _cm_to_ft(long double v) { + double m = v * CM; + return m / FOOT; +} + +// перевод из метров в сантиметры +double operator"" _m_to_cm(long double v) { + return v / CM; +} +// перевод из метров в дюймы +double operator"" _m_to_in(long double v) { + return v / INCH; +} +// перевод из метров в футы +double operator"" _m_to_ft(long double v) { + return v / FOOT; +} diff --git a/01_week/tasks/print_bits/print_bits.cpp b/01_week/tasks/print_bits/print_bits.cpp index a48a43c1..69e712a7 100644 --- a/01_week/tasks/print_bits/print_bits.cpp +++ b/01_week/tasks/print_bits/print_bits.cpp @@ -1,7 +1,30 @@ #include #include - +// value - целое число, его нужно вывести в двоичной форме +// bytes типа size_t потому что bytes это количсевто байт void PrintBits(long long value, size_t bytes) { - throw std::runtime_error{"Not implemented"}; + // по условию мы не можем выполнить функцию когда у нас + // количсевто байт = 0, либо количесвто байт > 8 + if (bytes == 0 || bytes > 8) { + return; + } + // Считаем общее количсевто бит, 1 байт = 1 бит + size_t bits = bytes * 8; + // выводим префикс "0b", дальше будет идти предствление числа + std::cout << "0b"; + // цикл будет выполняться bits раз + for (size_t i = 0; i < bits; ++i) { + size_t bit_index = bits - 1 - i; + // побитовый сдвиг, сравниваем младший бит с 1 и выводим результат + std::cout << ((value >> bit_index) & 1); + + // если i + 1 делится на 4 без остатка и это не полседний бит, + // ставим "'" + if ((i + 1) % 4 == 0 && (i + 1) != bits) { + std::cout << "'"; + } + } + // перевод строки + std::cout << '\n'; } diff --git a/01_week/tasks/quadratic/quadratic.cpp b/01_week/tasks/quadratic/quadratic.cpp index abf7d632..daa8eb20 100644 --- a/01_week/tasks/quadratic/quadratic.cpp +++ b/01_week/tasks/quadratic/quadratic.cpp @@ -1,6 +1,85 @@ -#include +#include +#include +#include void SolveQuadratic(int a, int b, int c) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file + // первый случай: если a = b = c = 0, то уравнение примнимает бесконченое количесвто решений + if (a == 0 && b == 0 && c == 0) { + std::cout << "infinite solutions"; + return; + } + + // второй случай: если a = b = 0, то уравнение решений не имеет + if (a == 0 && b == 0) { + std::cout << "no solutions"; + return; + } + + std::cout << std::setprecision(6); + + // третий случай: a == 0, b != 0 → b*x + c = 0 + if (a == 0) { + double x = -static_cast(c) / static_cast(b); // x = -c / b + + // Убираем возможный "-0" + if (x == -0.0) { + x = 0.0; + } + + std::cout << x; + return; + } + + // 4) четвертый случай: a неравно 0, то есть уже само квадартное уравнение + double A = static_cast(a); + double B = static_cast(b); + double C = static_cast(c); + + // Дискриминант + double D = B * B - 4 * A * C; + + // Нет вещественных корней + if (D < 0) { + std::cout << "no solutions"; + return; + } + + // Один вещественный корень + if (D == 0) { + double root = -B / (2 * A); + double x = static_cast(root); + + // Убираем "-0" + if (x == -0.0) { + x = 0.0; + } + + std::cout << x; + return; + } + + // если D > 0, то имеем два различных корня + double sqrtD = std::sqrt(D); + + double root1 = (-B - sqrtD) / (2 * A); + double root2 = (-B + sqrtD) / (2 * A); + + double x1 = static_cast(root1); + double x2 = static_cast(root2); + + // Убираем "-0" для каждого корня + if (x1 == -0.0) { + x1 = 0.0; + } + if (x2 == -0.0) { + x2 = 0.0; + } + + // выводим так, чтобы x1 < x2 + if (x1 > x2) { + std::swap(x1, x2); + } + + std::cout << x1 << ' ' << x2; +} diff --git a/01_week/tasks/rms/rms.cpp b/01_week/tasks/rms/rms.cpp index 6882f0a9..afc9b67c 100644 --- a/01_week/tasks/rms/rms.cpp +++ b/01_week/tasks/rms/rms.cpp @@ -1,7 +1,19 @@ -#include +#include #include - +#include double CalculateRMS(double values[], size_t size) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file + + // в случае пустовго массива возращаем 0.0 + if (size == 0 || values == nullptr) { + return 0.0; + } + + double sumSq = 0.0; + // сумма квадратов + for (size_t i = 0; i < size; ++i) { + sumSq += values[i] * values[i]; + } + // RMS + return std::sqrt(sumSq /size); +} diff --git a/02_week/tasks/func_array/func_array.cpp b/02_week/tasks/func_array/func_array.cpp index b327e68d..3b332cb6 100644 --- a/02_week/tasks/func_array/func_array.cpp +++ b/02_week/tasks/func_array/func_array.cpp @@ -1,6 +1,29 @@ #include +double ApplyOperations( + double a, + double b, + double (*mathOperations[])(double, double), // массив указателей, который принимает два числа типа double + size_t size // рамзер массива +) { + // если массив пуст - возвращаем 0.0 + if (size == 0) { + return 0.0; + } + // переменная для суммы мат операций + double sum = 0.0; + // проходим по каждому элементу массива + for (size_t i = 0; i < size; ++i) { -double ApplyOperations(double a, double b /* other arguments */) { - throw std::runtime_error{"Not implemented"}; + // если один из указателей массива пустой, то пропускаем + if (mathOperations[i] == nullptr) { + continue; // пропускаем пустую функцию + } + // вызываем i-ю функцию с числами a и b, кладем в v + double v = mathOperations[i](a, b); + // прибавляем к общей сумме + sum += v; + } + + return sum; } \ No newline at end of file diff --git a/02_week/tasks/last_of_us/last_of_us.cpp b/02_week/tasks/last_of_us/last_of_us.cpp index c7bf1a25..ae0eeec2 100644 --- a/02_week/tasks/last_of_us/last_of_us.cpp +++ b/02_week/tasks/last_of_us/last_of_us.cpp @@ -1,6 +1,29 @@ #include +// для случая (nullptr, nullptr) +int* FindLastElement(std::nullptr_t, std::nullptr_t, bool (*)(int)) { + return nullptr; +} +// Функция ищет последний элемент, удовлетворяющий предикату, +// в диапазоне [begin, end). Возвращает указатель на найденный элемент. +// Если элемент не найден или диапазон некорректный, возвращает end. +int* FindLastElement(int* begin, int* end, bool (*predicate)(int)) { + if (begin == nullptr || end == nullptr || begin > end || predicate == nullptr) { + return end; + } + // Перебираем элементы с конца диапазона + for (int* i = end - 1; i >= begin; --i) { + if (predicate(*i)) { + return i; // сразу возвращаем первый подходящий справа + } + } -/* return_type */ FindLastElement(/* ptr_type */ begin, /* ptr_type */ end, /* func_type */ predicate) { - throw std::runtime_error{"Not implemented"}; + // если ничего не нашли, то возвращаем end + return end; +} + +// Перегрузка для const int*. +// Снимаем const, чтобы переиспользовать логику из версии для int*. +const int* FindLastElement(const int* begin, const int* end, bool (*predicate)(int)) { + return FindLastElement(const_cast(begin), const_cast(end), predicate); } \ No newline at end of file diff --git a/02_week/tasks/longest/longest.cpp b/02_week/tasks/longest/longest.cpp index 04b3c354..e4a78575 100644 --- a/02_week/tasks/longest/longest.cpp +++ b/02_week/tasks/longest/longest.cpp @@ -1,6 +1,57 @@ -#include +#include +// Функция ищет самую длинную подпоследовательность одинаковых символов +// в диапазоне [begin, end). Длину подпоследовательности записывает в count. +// Возвращает указатель на начало найденной подпоследовательности. +// В случае ошибки возвращает nullptr. В count записывает 0. +char* FindLongestSubsequence(char* begin, char* end, size_t& count) { + // проверка корректнсти + if (begin == nullptr || end == nullptr || begin >= end) { + count = 0; + return nullptr; + } -/* return_type */ FindLongestSubsequence(/* ptr_type */ begin, /* ptr_type */ end, /* type */ count) { - throw std::runtime_error{"Not implemented"}; + char* best_start = begin; // начало лучшей подпоследовательности + size_t best_length = 1; // длина лучшей последовательности + + char* curr_start = begin; // начало текущей последовательности + size_t curr_length = 1; // длина текущей последовательности + + // Перебираем символы, начиная со второго + for (char* i = begin + 1; i < end; ++i) { + // тут проверка на совпадения + // если совпадает, то увеличивам длину текущей подпоследовательности curr_length + if (*i == *(i - 1)) { + ++curr_length; + } else { + // Если текущая последовательность строго длиннее лучшей, то + // обновляем результат. При равных длинах не обновляем, так как + // по условию возвращается первая + if (curr_length > best_length) { + best_start = curr_start; + best_length = curr_length; + } + curr_start = i; // начинаем новую подпоследовательность с текущего символа + curr_length = 1; // начало новой подпоследовательность + } + } + // Проверяем последнюю подпоследовательность (она могла закончится концом строки) + if (curr_length > best_length) { + best_length = curr_length; + best_start = curr_start; + } + // записываем в count лучшую длину + count = best_length; + return best_start; +} + + +// Перегрузка для const char*. +// Код поиска наибольшей подпоследовтаельности уже реализован в версии для char*, +// поэтому здесь будем использовать его, +// временно снимаем const, чтобы вызвать существующую логику +const char* FindLongestSubsequence(const char* begin, const char* end, size_t& count) { + char* result = FindLongestSubsequence(const_cast(begin), const_cast(end), count); + return result; } + diff --git a/02_week/tasks/swap_ptr/swap_ptr.cpp b/02_week/tasks/swap_ptr/swap_ptr.cpp index 93db625d..dc835ee2 100644 --- a/02_week/tasks/swap_ptr/swap_ptr.cpp +++ b/02_week/tasks/swap_ptr/swap_ptr.cpp @@ -1,6 +1,57 @@ #include +#include +// Есть три вида Template parameters: +// 1. constant template parameter +// 2. type template parameter (нам нужен этот) +// 3. template template parameter +template // параметр-тип +void SwapPtr(T*& a, T*& b) { + T* tmp = a; // сохраняем адрес из a + a = b; // кладём в a адрес из b + b = tmp; // в b кладём старый адрес a +} -void SwapPtr(/* write arguments here */) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file +// версия без использования парметр-тип: +/* void SwapPtr(int*& a, int*& b) { + int* tmp = a; + a = b; + b = tmp; +} + +void SwapPtr(const int*& a, const int*& b) { + const int* tmp = a; + a = b; + b = tmp; +} + +void SwapPtr(int**& a, int**& b) { + int** tmp = a; + a = b; + b = tmp; +} +*/ + + + +// это для визуальной проверки +/* int main() { + int x = 1; + int y = 2; + + int* a = &x; + int* b = &y; + + std::cout << "До SwapPtr\n"; + std::cout << "Адрес a = " << a << ", *a = " << *a << "\n"; + std::cout << "Адрес b = " << b << ", *b = " << *b << "\n"; + + SwapPtr(a, b); + + std::cout << "После SwapPtr\n"; + std::cout << "Адрес a = " << a << ", *a = " << *a << "\n"; + std::cout << "Адрес b = " << b << ", *b = " << *b << "\n"; + + return 0; +} */ + \ No newline at end of file diff --git a/05_week/05_memory_move.md b/05_week/05_memory_move.md new file mode 100644 index 00000000..5cffaf03 --- /dev/null +++ b/05_week/05_memory_move.md @@ -0,0 +1,1007 @@ +# Лекция 5. Память программы на C++. Семантика перемещения + +1. [Память программы (процесса)](#process_memory) + - [Стек](#stack) + - [Куча](#heap) + - [Сегмент BSS данных (Block Started by Symbol)](#bss_segment) + - [Сегмент данных (Data Segment)](#data_segment) + - [Сегмент данных для чтения (RODATA Segment)](#rodata_segment) + - [Сегмент кода программы (Text Segment) ](#text_segment) + - [Сегмент локальных переменных потока (Thread Local Segment)](#tls_segment) +1. [Продолжительность хранения (_Storage duration_)](#storage_duration) + - [Static storage duration](#static_storage_duration) + - [Automatic storage duration](#automatic_storage_duration) + - [Dynamic storage duration](#dynamic_storage_duration) + - [Thread storage duration](#thread_storage_duration) +1. [Связывание (_linkage_)](#linkage) + - [Внешнее связывание (_external linkage_)](#external_linkage) + - [Внутренее связывание (_internal linkage_)](#internal_linkage) + - [Отсутствие связывания (_no linkage_)](#no_linkage) + - [Модульное связывания (_module linkage_) C++20](#module_linkage) +1. [Ключевое слово `static`](#static) + - [Вне класса. Глобальная статическая переменная](#global_static_var) + - [Вне класса. Локальная статическая переменная](#local_static_var) + - [Вне класса. Глобальная статическая функция](#global_static_function) + - [Внутри класса. Статическое поле](#static_field_in_class) + - [Внутри класса. Статический метод](#static_function_in_class) +1. [Initialization Order Fiasco](#initialization_order_fiasco) +1. [Ключевое слово `inline`](#inline) +1. [Работа с динамической памятью](#cpp_dynamic_memory) + - [Операторы `new` и `delete`](#new_delete) + - [Операторы `new[]` и `delete[]`](#new_delete_array) + - [Переопределение операторов `new` и `delete`](#new_delete_overloading) +1. [Ключевое слово `noexcept`](#noexcept) +1. [Категория выражений ([value categories](https://en.cppreference.com/w/cpp/language/value_category.html))](#value_categories) + - [rvalue-ссылка](#rvalue_reference) + - [Связывание ссылок](#reference_binding) + - [Перегрузка функции по ссылке](#function_overloading_by_reference) + - [Квалификация метода по виду ссылки ](#reference_qualifiers_class_method) + - [Temporary materialization (С++17)](#temporary_materialization) +1. [Семантика перемещения](#move_semantics) + - [Функция `std::move`](#std_move) + - [Правило нуля (_Rule of Zero_)](#rule_of_zero) + - [Правило пяти (_Rule of Five_)](#rule_of_five) + - [Правила для специальных методов](#rules_for_specific_methods) +1. [Copy Elision](#copy_elision) + - [Return Value Optimization (RVO)](#rvo) + - [Named Return Value Optimization (NRVO)](#nrvo) + + +## Память программы (процесса) + +Программа имеет виртуальное адресное пространство. При запуске программа +не полностью загружается в оперативную память. Операционная система (ОС) +настраивает таблицы страниц и выделяет области памяти для программы. +Таблицы страниц служат для отображения виртуальных адресов программы на C++ +в физические адреса оперативной памяти. + +В языке C++ упрощенно можно выделить следующие сегменты памяти: + +- **автоматический** - представлен стеком, служит для автоматического выделения + и освобождения памяти под локальные переменные, аргументы функции. +- **статический** - для глобальных и статических переменных, существует всю жизнь + программы +- **динамический** - представлен кучей, позволяет получать память в использование + во время выполнения программы и требует управления выделением и освобождением + памяти +- **потоковый** - для специально помеченных локальных переменных потока, существует + всю жизнь потока + +Память процесса можно разделить на следующие сегменты: + +- Stack (Стек) +- TLS (Thread local segment) +- Heap (Куча) +- BSS (Block Started by Symbol) +- Data Segment +- RODATA Segment (read only) +- Text Segment (Code) + +### Стек + +Стек имеет ограниченный размер, который зависит от ОС и её настроек. По умолчанию +размер составляет 1-8 Мб (Windows, Linux). Стек организован по принципу LIFO + +Выделяемая память на стеке **НЕ** инициализируется по умолчанию. + +При вызове функции на стеке формируется стек фрейм, в котором размещаются +параметры функции, локальные переменные, адрес возврата, сохраненные регистры. + +Компилятор **НЕ** гарантирует, что переменные на стеке будут лежать в порядке +объявления. + +При выходе из области видимости вызываются деструкторы объектов в порядке +обратном созданию + +При выходе из функции указатель текущего фрейма стека (stack pointer) просто +сдвигается, тем самым память автоматически считается свободной. + +Переполнение стека приводит к ошибке stack overflow -> segmentation fault. + +К переполнению может приводить как размещение слишком больших массивов, так +и глубокий рекурсивный вызов функий. + +### Куча + +Динамическая память (куча) представляет собой участок памяти, требующий ручного +управления. Динамическая память выделяется во время выполнения программы и имеет +продолжительность хранения, пока не будет освобождена. + +Куча медленнее стека поскольку при выделении может происходить запрос к +менеджеру памяти ОС (системный вызов) во время выполнения программы. + +### Сегмент BSS данных (Block Started by Symbol) + +Данный сегмент хранит глобальные и статические переменные, которые не имеют +явной инициализации или инициализированы нулем. + +Загрузчик ОС при запуске программы выделяет память под эти переменные и зануляет +её, поэтому они не занимают места в бинарном файле, что позволяет экономить место. + +### Сегмент данных (Data Segment) + +Данный сегмент хранит глобальные и статические переменные, которые явно +инициализированы ненулевыми значениями. Переменные занимают место в бинарном +файле + +### Сегмент данных для чтения (RODATA Segment) + +Как следует из названия в данном сегменте лежат данные для чтения. Поэтому +попытка изменения данных приводит к ошибкам компиляции или к UB, падению +программы при попытке изменить константу. Продолжительность хранения данного +сегмента статическая - все время жизни программы + +В данном сегменте лежат именованные константы, строковые литералы. +Строковые литералы могут лежать в единственном экземпляре. + +### Сегмент кода программы (Text Segment) + +В данном сегменте располагается машинный код программы. В данном сегменте +записаны функции, методы и точка входа в программу `main`. Как правило, +данный сегмент предназначен только для чтения и является разделяемым +(_shared_) между потоками. + +### Сегмент локальных переменных потока (Thread Local Segment) + +В данном сегменте располагаются локальные переменные потока. Для таких переменных +при объявлении используется ключевое слово `thread_local` перед типом. У каждого +потока свои копии переменных, а их время жизни соответствует времени жизни потока. + +## Продолжительность хранения (_Storage duration_) + +В языке C++ с сегментами памяти тесно связано понятие storage duration. + +Продолжительность хранения (storage duration) - период, в течение которого +зарезервирована память для объекта. + +Продолжительность хранения это свойство объекта, которое определяет минимальный +потенциальный срок жизни выделенной памяти под объект. + +В языке C++ выделены следующие виды продолжительности хранения + +- static +- automatic +- dynamic +- thread + +### Static storage duration + +Static storage duration - память выделяется и освобождается независимо от области +видимости объекта и продолжает существовать все время выполнение процесса. + +Соответствует сегментам памяти `.bss`, `.data`, `.rodata` + +Глобальные, статические переменные, в том числе объявленные внутри `namespace` +имеют static storage duration + +Создается до входа в `main` (для локальных статических переменных при первом +входе) + +Порядок инициализации между единицами трансляции **НЕ** определен. Следовательно, +некорректно инициализировать переменную со статическим storage duration, переменной +с аналогичным storage duration из другой единицы трансляции (файла) + +```c++ +int global; +int global_init = 18; +static int static_value = 10; +extern int value; + +namespace utils { + const double IN_TO_CM = 2.54; +} + +int& get_static() { + static int instance; + return &instance; +} +``` + +### Automatic storage duration + +Automatic storage duration - память выделяется при входе в область видимости и +освобождается при выходе из области видимости, причем вызываются деструкторы. + +Представлен стеком. + +```c++ +void function(int a, double b, const char* name) { + std::string info(name); + { + double num = a * b; + info += std::to_string(num); + } + std::cout << info << std::endl; +} +``` +- переменные a, b, name, info, num - располагаются на стеке + +### Dynamic storage duration + +Dynamic storage duration - управление памяти осуществляется вручную. + +Представлен кучей + +```c++ +int* ptr = new int(5); +int* arr = new int[5]{1, 2, 3}; +delete ptr; +delete[] arr; +``` + +### Thread storage duration + +Thread storage duration - память выделяется при запуске потока и освобождается +при его завершении. + +У каждого потока своя копия + +```c++ +thread_local int value = 18; +``` + +## Связывание (_linkage_) + +Связывание определяет видимость идентификаторов между единицами трансляции. +Связывание не относится к памяти напрямую, а затрагивает связывание имён на +этапе линковки + +### Внешнее связывание (_external linkage_) + +**External linkage** - внешнее связывание означает, что идентификатор (имя) +виден в других единицах трансляции. По умолчанию для сущностей C++ в глобальной +области видимости. + +### Внутренее связывание (_internal linkage_) + +**Internal linkage** - внутреннее связывание, означает, что идентификатор (имя) +виден только в текущей единице трансляции. Для `static` переменных, `const` +переменных, имен шаблонных параметров. + +Таким образом `const` в заголовочном файле, включаемый в разные `.cpp` файлы, +представлен копией в каждой единице трансляции. + +Для внешнего связывания `const` переменной используется ключевое слово `extern` + +Для внешнего связывания константы в C++17 используется ключевое слово +`inline constexpr` + +### Отсутствие связывания (_no linkage_) + +**No linkage** - отсутствует связывание, означает, что идентификатор (имя) виден +только в области видимости. Для параметров функций, имен шаблонных параметров, +локальных переменных. + +### Модульное связывания (_module linkage_) C++20 + +В языке C++20 появились модули `module` и вместе с ними появился новый вид связывания + +**Module linkage** (C++20) - модульное связывания означает, что идентификатор (имя) +виден в пределах модуля и не виден извне. + +Модуль может включать несколько единиц трансляции. + +## Ключевое слово `static` + +Ключевое слово `static` применяется к идентификатору переменной или +функции и имеет разные смыслы в зависимости от контекста использования. + +### Вне класса. Глобальная статическая переменная + +**Глобальная переменная**, помеченная ключевым словом `static`, имеет +статическую продолжительность хранения (static storage duration) и +внутреннее связывание (internal linkage), то есть видна только в текущей +единице трансляции. Что позволяет скрыть реализацию в .cpp файле и избежать +конфликтов **ODR** (_One Definition Rule_) + +```c++ +static int value = 18; +``` + +**НО** если определить её в `.h` файле, то каждый включающий её `.cpp` +файл будет иметь копию, видную только в пределах данного файла. + +### Вне класса. Локальная статическая переменная + +**Локальная статическая переменная** (внутри функции) имеет _static +storage duration_ и область видимости внутри функции, а также +инициализируется только **один раз** при первом вызове функции. + +```c++ +void f() { + static int counter = 0; + ++counter; + std::cout << "f() call count = " << counter << std::endl; +} +``` +- **НЕ** видна вне функции + +Как правило используется в качестве кэша, счетчика, синглтона +(singleton), при ленивой инициализации (lazy initialization) + +### Вне класса. Глобальная статическая функция + +**Глобальная функция**, помеченная ключевым словом `static`, также +имеет внутреннее связывание и не видна из других единиц трансляции +(.cpp файлов) + +```c++ +static void inner_helper(); +``` + +В современном C++ заменяют `static` в данном контексте на размещение +глобальной функции или переменной в безымянном пространстве имен +`namespace` + +### Внутри класса. Статическое поле + +**Переменная внутри класса**, объявленная с ключевым словом `static`, имеет +статическую продолжительность хранения (static storage duration) и внешнее +связывание (external linkage), если нет inline. + +Статическая переменная класса принадлежит классу, а не конкретному экземпляру. + +Доступ к статическому полю принято осуществлять посредством имени класса и оператора +`::`. Также доступно через экземпляр класса и оператор `.` + +Статическое поле является объявлением. Требует определения только в одном `.cpp`, +поэтому инициализация в теле класса вызывает ошибку **ODR violation**. + +```c++ +// .h +class C { + static int counter; // declaration + // static int counter = 0; // compile error +}; +// .cpp +int C::counter = 0; // definition +``` + +Инициализация `static` поля внутри класса разрешена для интегральных констант. + +**НО** для получения адреса константного статического поля необходимо определение +**ВНЕ** класса, даже если значение проинициализировано. + +```c++ +// .h +class C { + static const int multipliyer = 2; // declaration + // static const double PI = 3.14; // error double is not integral +}; +// .cpp +const int C::multipliyer; // definition +const int* ptr = &C::multipliyer; // work after definition +``` + +Инициализация `static` поля внутри класса разрешена для `constexpr` полей (C++11). + +```c++ +// .h +class C { + static constexpr int multipliyer = 2; // declaration + static constexpr double PI = 3.141592653589793; //ok +}; +// .cpp +constexpr int C::multipliyer; // definition +const int* ptr = &C::multipliyer; // work after definition +``` +- Но для получения адреса необходимо определение **ВНЕ** класса до C++17. + Начиная с C++17 `constexpr` по умолчанию является `inline` и определение + **ВНЕ** класса **НЕ** требуется + +Инициализация `static` поля внутри класса разрешена для `inline static` полей (C++17). +Является определением, определение **ВНЕ** класса **НЕ** требуется, разрешено +в заголовке. + +```c++ +class C { + inline static int multipliyer = 2; + inline static std::string default_name = "name"; +}; +``` + +### Внутри класса. Статический метод + +**Функция внутри класса**, объявленная с ключевым словом `static` не привязана +к какому-либо экземпляру (объекту) класса, а логически принадлежит всему классу. +Следовательно, внутри функции нет указателя на объект `this`. + +```c++ +struct A { + static int f(); +}; +``` + +Внутри функции можно обращаться только к `static` полям, к обычным нельзя + +Вызвать функцию можно с указанием принадлежности классу, используя оператор `::` + +```c++ +int value = A::f(); +``` + +## Initialization Order Fiasco + +Initialization Order Fiasco - Ошибка порядка статической инициализации, +которая возникает, когда один объект со _static storage duration_ использует +для инициализации другой объект из другого файла с аналогичным _storage duration_, +поскольку порядок инициализации между единицами трансляции не определен. + +Следующий код вызывает **UB**: + +```c++ +// a.cpp +extern int y; +int x = y + 1; + +// b.cpp +int y = 10; +``` +- порядок инициализации внутри одной единцы трансляции сверху вниз + +## Ключевое слово `inline` + +Ключевое слово `inline` означает, что можно иметь несколько определений, +если они одинаковы. + +Для переменных (C++17) это значит, что это одно логическое определение и можно +размещать в заголовке. + +Таким образом, использование `inline static` используется для вспомогательных +функций, определяемых в заголовочном файле. + +## Работа с динамической памятью + +При выполнении операции выделения памяти процесс обращается к ОС, ОС находит +свободные физические кадры памяти и обновляет таблицу страниц, чтобы отобразить +виртуальный адрес (возвращаемый в программе) на этот физический кадр. Если +физическая память заканчивается, ОС перемещает старые страницы на диск (подкачка), +помечая запись и загружает новые. Если запрошенная по адресу переменная не +находится в таблице страниц, происходит _page fault_ и ОС загружает нужную страницу +обратно в память. Поэтому работа с динамической памятью медленнее, чем со стеком. + +Выделенная процессу память будет считаться занятой, пока с помощью операции +освобождения памяти она не будет явно освобождена и отдана ОС. Если динамическую +память не освободить, то она будет освобождена только по факту завершения процесса + +При работе с динамической памятью могут возникать следующие проблемы: + +- Утечки памяти (memory leak) - когда выделенная память не освобождается и уже + не используется. Такая память будет считаться ОС занятой до тех пор, пока не + завершится весь процесс (программа). + +- Двойное удаление (double free) - когда производится вызов оператора освобождения + памяти дважды для одного указателя, что приведет к ошибке и падению программы + +- Висячий указатель (dangling pointer) - когда указатель, указывает на область + памяти, которая уже была освобождена (удалена), но сам указатель не был обнулен, + либо это другой указатель на ту же область памяти. + +### Операторы `new` и `delete` + +Для выделения динамической памяти используется оператор `new`, а для освобождения +оператор `delete`. + +Синтаксис: `* = new ;` +Синтаксис с инициализацией: +- `* = new ();` +- `* = new {};` + +```c++ +int* ptr = new int; +int* ptr_init = new int(5); +delete ptr; +delete ptr_init; +``` + +### Операторы `new[]` и `delete[]` + +Также есть соответствующая версии операторов для работы массивами `new[]`, +`delete[]`. Обязательно необходимо освобождать память с использованием парного +оператора. + +Синтаксис: `* = new [];` +Синтаксис с инициализацией аналогичен созданию обычных массивов. + +```c++ +int* arr = new int[10]; +delete[] arr; +``` + +### Переопределение операторов `new` и `delete` + +Глобальный оператор `new`: + +```c++ +void* operator new(std::size_t); +void operator delete(void*) noexcept; +``` + +Оператор `new` внутри класса: + +```c++ +struct Struct { + static void* operator new(std::size_t); + static void operator delete(void*) noexcept; +}; +``` + +Для вызова глобальных операторов в таком случае можно воспользоваться +оператором разрешения области видимости `::operator new` , `::operator delete` + +```c++ +static void* Struct::operator new(std::size_t bytes) { + std::cerr << "allocate: " << bytes << " bytes\n"; + return ::operator new(bytes); +} +static void Struct::operator delete(void* ptr, std::size_t bytes) noexcept { + std::cerr << "deallocate: " << bytes << " bytes\n"; + ::operator delete (ptr); +} +``` + +Оператор placement `new` переопределить нельзя: + +```c++ +void* operator new(std::size_t, void*) noexcept; +``` + +## Ключевое слово `noexcept` + +Ключевое слово `noexcept` является спецификатором функции (в том числе метода, +оператора) и оператором, проверяющим на этапе компиляции может ли выражение +выбрасывать исключение `noexcept()`. + +Спецификатор `noexcept` пишется после сигнатуры функции и перед `{`. +Спецификатор сообщает компилятору, что код внутри не выбрасывает исключений, +и позволяет ему выполнить оптимизации (убрать код для раскрутки стека). + +Если исключение всё же будет выброшено из помеченной функции, то вызовется +`std::terminate`, который завершит процесс. + +Хорошим тоном считается писать `noexcept` там, где действительно код не может +выбросить исключение. + +Правила использования: + +- Деструкторы по умолчанию `noexcept` +- Функции освобождения памяти (`free`, `delete`) должны быть `noexcept` +- Функции обмена (`swap`) должны быть `noexcept` +- Хорошими кандидатами на использование `noexcept` являются `const` методы +- Move-операции должны быть `noexcept`, когда это возможно +- **НЕ** следует использовать `noexcept(false)` без веской причины + +## Категория выражений ([value categories](https://en.cppreference.com/w/cpp/language/value_category.html)) + +Помимо типа каждое выражение в C++ характеризуется категорией. + +Классификация до C++11: + +- lvalue - имеет адрес, может быть слева от присваивания `=` +- rvalue - временное значение, справа от присваивания `=` + +Современная классификация (C++11) построена на двух свойствах: + +- Идентичность (I)- параметр, по которому можно понять ссылаются ли два объекта + на одну и ту же сущность (например, наличие адреса в памяти) +- Перемещаемость (M) - возможно ли переместить объект, отдать владение ресурсом + +В основу классификации легли три первичных категории + +- lvalue (locator/left value) +- xvalue (expiring value) - временный материализованный объект (с истекающим временем жизни) +- prvalue (pure rvalue) + +```text + glvalue(I) rvalue(M) + / \ / \ +lvalue(I~M) xvalue(IM) prvalue(~IM) +``` + +### rvalue-ссылка + +В C++11 вводится rvalue-ссылка `&&`. + +Основные отличия rvalue-ссылка `&&` от lvalue-ссылки `&`: + +- можно проинициализировать только rvalue выражением +- при возврате из функции `&&` возвращается rvalue выражение. +- продлевает жизнь временным объектам + +Во всем остальном ведет себя также как ссылка. +Сам по себе идентификатор, объявленный rvalue-ссылкой, является lvalue. + +```c++ +int l = 18; +int&& r = l; // compilation error (l is not rvalue) +int&& x = 18; // ok +int&& y = x; //compilation error (x is not rvalue) +int&& m = std::move(l); // ok +int&& sc = static_cast(x); // ok +m = 0; // ok l = 0, m = 0 +``` + +В языке присутствует базовое неявное приведение _lvalue-to-prvalue_ + +```c++ +int x, y; +x = x + 1; // lvalue = prvalue +y = x; // lvalue = lvalue to prvalue +y = std::move(x); // lvalue = xvalue +``` + +### Связывание ссылок + +Rvalue-ссылка **НЕ** может быть связана с **lvalue** +Lvalue-ссылка **НЕ** может быть связана с **rvalue** + +```c++ +int x = 1; +int&& y = x * 3; //ok +int&& b = x; //fail, not rvalue + +int& c = x * 3; // fail, not lvalue; +const int& d = x * 3; //ok, lifetime prolongation for const ref + +int&& e = y; // fail, not rvalue +int& f = y; // ok +``` + +### Перегрузка функции по ссылке + +Функция может быть перегружен по типу принимаемой ссылке: + +```c++ +int foo(int& v); // 1 +int foo(int&& v); // 2 +int foo(const int& v); // 3 +int foo(const int&& v); // 4 + +int x = 1; + +foo(x); // -> 1 +foo(1); // -> 2 + +const int y = 1; + +foo(y); // -> 3 +foo(std::move(y)); // -> 4 +``` + +### Квалификация метода по виду ссылки + +Метод может быть перегружен, по факту вызова для **lvalue** или **rvalue** ссылки. + +Для этого после закрывающей скобки аргументов добавляется квалификатор **&** или **&&** +Таким образом, можно определить различное поведение при вызове функции от временного +объекта `&&` и постоянного `&` + +```c++ +struct S { + int foo() &; + int foo() &&; +}; +``` + +Оператор присваивания можно пометить одним амперсандом `&` и не создавать версию +с `&&`, чтобы к временному объекту нельзя было присваивать, тогда будет +недоступно присваивание к временному объекту, что достаточно логично + +```c++ +struct S { + S() = default; + S& operator=(const S& other) &; +}; + +S a; +S() = a; // compile error +``` + +### Temporary materialization (С++17) + +Материализация временного объекта (_temporary materialization_) представляет собой +неявное преобразование из _prvalue_ в _xvalue_. + +Материализация временного объекта происходит: + +- при связывании ссылки с чисто временным объектом (категории prvalue) +- при доступе к нестатическому полю класса, prvalue объекта +- при вызове нестатических методов от prvalue объекта (поскольку в метод неявно + передается указатель `this`) +- при выполнении преобразования array-to-pointer или обращению по индексу `[]` + к prvalue массиву +- при инициализации объекта `std::initializer_list` посредством списка инициализации + в фигурных скобках +- когда prvalue появляется в отбрасывающем результат выражении (игнорируем результат + prvalue выражения, возвращающего тип какого-либо класса) + +1. Связывание ссылки с чисто временным объектом + +```c++ +const int& r1 = 10; // prvalue 10 is materialized to get cref +int&& r2 = 5 + 3; // prvalue from expr '5+3' is materialized to get && + +void foo(const std::string&); +foo("hello"); // std::string materializes from "hello" to get cref +``` + +2. Доступ к нестатическому полю класса, prvalue объекта + +```c++ +struct Point { int x = 0, int y = 0}; +int val = Point{3, 4}.x; // Point{3,4} is materialized to access.x; +``` + +3. Вызов нестатического метода от rvalue объекта + +```c++ +struct Logger { + void log() const { std::cout << "Logging\n"; } +}; +Logger().log(); // Logger() is materialized to call log(), need this. +std::string("hello").length(); // the same case +``` + +4. Обращение к prvalue массиву `[]` или при приобразовании array-to-pointer + +```c++ +void bar(const int*); +bar((int[]){10, 20}); + +int* p1 = (int[3]){1, 2, 3};. +int val = ((int[]){10, 20, 30})[1]; +``` + +5. Инициализация объекта `std::initializer_list` + +```c++ +#include +void func(std::initializer_list lst) {} +func({1, 2, 3}); +``` + +6. Когда prvalue в discarded-value expression + +```c++ +struct Widget { + Widget() { std::cout << "Created\n"; } + ~Widget() { std::cout << "Destroyed\n"; } +}; +Widget(); // materialized to the end of expression ; +``` + +Материализации временного объекта **НЕ** возникает, когда используется prvalue того же +типа (при `direct-initialization` или `copy-initialization`). Такие объекты создаются +напрямую из инициализирующего prvalue. Это является гарантированным _copy elision_, +начиная с C++17 + +## Семантика перемещения + +Чтобы избежать излишнего копирования, когда создается объект из объекта, который +больше не нужен, в языке реализована семантика перемещения (move-семантика). + +Семантика перемещения позволяет забрать у другого объекты ресурсы, которыми +он владеет. Причем, объект, у которого забрали данные, должен остаться в +консистентном состоянии, то есть после перемещения из него данных, его +состоянии было корректным (часто дефолтным, нулевым) и им можно было продолжать +пользоваться. + +Начиная с C++11 у класса появляется перемещающий конструктор и перемещающий +оператор присваивания. + +```c++ +Class(Class&& other); +Class& operator=(Class&& other); +``` + +Компилятор генерирует move-конструктор и move оператор присваивания, вызывая +соответствующие move операции для полей класса. + +Для примитивных типов перемещающие методы выполняют **копирование**. + +Владение ресурсом подразумевает, например указатель на динамическую память внутри +класса и выделение памяти в конструкторе и освобождение в деструкторе. Пусть +экземпляр класса создается на стеке, а данные в динамической памяти. Копирование +по умолчанию подразумевает копирование значений всех полей, в случае указателя +скопируются не данные, а указатель (адрес на выделенную память). В такой ситуации +возникает проблема двойного удаления по указателю, так как два объекта имеют +одинаковые указатели на одну память и при выходе из области видимости объектов в +деструкторе будет двойное освобождение памяти по указателю. + +Для такого класса переопределяется конструктор копирования и оператор присваивания +копированием, чтобы данные в динамической памяти действительно копировались: + +```c++ +class String { +public: + String(size_t size) : size_(size), cap_(size), data_(new char[size + 1]) { + std::fill_n(data, size + 1, '\0'); + } + String(const String& other) : + size_(other.size_), + cap_(other.size_), + data_(new char[other.size_ + 1]) { + std::strcpy(data_, other.data_); + } + String& operator=(const String& other) { + if (this != &other) { + delete[] data; + size = other.size; + cap_ = other.size; + data_ = new char[size + 1]; + std::strcpy(data_, other.data_); + } + return *this; + } + ~String() { delete[] data_; } + + size_t cap_; + size_t size_; + char* data_; +}; +``` + +Когда объект больше не нужен, а его данные нужны, или как минимум нужна выделенная +память, чтобы не выделять её снова, можно переместить ресурс (память, файловый +дескриптор, сокет и т.д.) из одного объекта в другой, а не копировать их. +Это обеспечивает значительный прирост производительности, особенно для объектов, +владеющих "тяжелыми" ресурсами. + +Но объект из которого забирают ресурс должен остаться в консистентном состоянии +и его можно было использовать + +```c++ + String(String&& other) noexcept + : cap_(other.cap_), size_(other.size_), data_(other.data_) { + // необходимо оставить other в валидном состоянии + other.cap_ = 0; + other.size_ = 0; + other.data_ = nullptr; + } + String& operator=(String&& other) noexcept { + if (this != &other) { + // очищаем свои данные + delete[] data_; + // копируем + cap_ = other.cap_; + size_ = other.size_; + // забираем данные + data_ = other.data_; + // необходимо оставить other в валидном состоянии + other.cap_ = 0; + other.size_ = 0; + other.data_ = nullptr; + } + return *this; + } +``` + +Вызвать данные конструкторы можно следующим образом: + +```c++ +String str(1000); +auto str_move = std::move(str); // call move ctor + +String str_other(10); +String str_moved(100); +str_moved = std::move(str_other); // call move assignment +``` + +### Функция `std::move` + +Функция `std::move` необходима для того, чтобы при вызове функции от объекта +перенаправить компилятор на использование версии с rvalue-ссылкой. + +Функция действует на этапе компиляции и всё что она делает, это возвращает +rvalue-ссылку на переданный ей объект + +```c++ +std::string str = "I will be moved"; +auto str_copy = str; +auto str_moved = std::move(str); // call move ctor for string +std::cout << str_copy == str_moved) << std::endl; // 1 (true) +std::cout << str.empty() << std::endl; // 1 (true) +``` + +### Правило нуля (_Rule of Zero_) + +"Правило нуля" - принцип программирования на C++, который гласит, что класс +**НЕ** должен определять ни один из пяти специальных методов, если их можно +автоматически сгенерировать компилятором: + +- деструктор +- конструктор копирования +- оператор присваивания копированием +- конструктор перемещения +- оператор присваивания перемещением + +Если ваш класс **НЕ** управляет ресурсами (памятью), а его данные — это простые типы +или классы, которые сами управляют ресурсами (например, `std::string`, `std::vector`, +умные указатели), то вам **НЕ** нужно писать свои специальные методы. + +Класс **НЕ** должен самостоятельно управлять ресурсами, если это не класс управления +ресурсами. + +### Правило пяти (_Rule of Five_) + +"Правило пяти" - принцип программирования на C++, который гласит, что если +пользовательский класс управляет ресурсами (память, файловые дескрипторы) и +определяет хотя бы один из пяти специальных методов, то он **должен явно +определить все пять**: + +- деструктор +- конструктор копирования +- оператор присваивания копированием +- конструктор перемещения +- оператор присваивания перемещением + +Такой подход позволяет избежать ошибок управления памятью (висячие указатели, +двойное удаление). + +До возникновения семантики перемещения в C++11 использовали "Правило трёх". + +### Правила для специальных методов + +Компилятор генерирует специальные методы самостоятельно в зависимости от условий. + +Деструктор по умолчанию создается: + +- default если нет ни одного пользовательского деструктора + +Конструктор копирования (аналогично копирующее присваивание): + +- delete если его нет, но есть пользовательское перемещение или move ctor +- иначе default, если его нет (но если при этом есть деструктор или парный + копирующий метод, то deprecated) + +Перемещающий конструктор (аналогично перемещающее присваивание) + +- delete если его нет, но при этом есть парный перемещающий метод или + copy ctor, или copy operator=, или dtor (при наследовании если деструктор + объявлен virtual) +- иначе default, если его нет + +## Copy Elision + +Copy elision (пропуск копирования) в C++ — это оптимизация компилятора, +устраняющая создание временных объектов при возврате из функций или передаче по +значению, что исключает вызов конструкторов копирования/перемещения. + +Начиная с C++17 компилятор обязан применять _copy elision_ при инициализации +объекта результатом функции, возвращающей тот же тип, что повышает +производительность и делает код более эффективным. + +Оптимизация позволяет не тратить время на копирование/перемещения, а деструкторы +для временных объектов не вызываются. + +### Return Value Optimization (RVO) + +**Return Value Optimization (RVO)** - оптимизация компилятора, позволяющая +не создавать локальный объект, который используется в качестве возвращаемого +значения, а конструировать возвращаемый объект на месте вызова функции. Такая +оптимизация позволяет устранить лишний вызов конструктора копирования или +перемещения. + +RVO применима, если возвращаемое значение prvalue-выражение, то есть временный +объект. + +Начиная с _C++17_ RVO - это не оптимизация, а правило, которое должно выполняться +компилятором. + +### Named Return Value Optimization (NRVO) + +**Named Return Value Optimization (NRVO)** - оптимизация компилятора, аналогичная +RVO, но действующая для локального lvalue-объекта, имеющего имя, и возвращаемого +из функции. + +На месте вызова функции вставляется инициализация объекта, принимающего +возвращаемое значение функции. В аргументы функции добавляется указатель на +данный объект и все вычисления возвращаемого значения выполняются над объектом +под указателем. + +NRVO может быть применена только когда тип возвращаемого объекта и тип +возвращаемого значения сигнатуры функции точно совпадают + +По этой причине **НЕ** следует возвращаемое значение оборачивать в `std::move()`, +так как тип возвращаемого значения не будет совпадать и, следовательно, +оптимизация не будет применена. diff --git a/05_week/05_memory_move.pdf b/05_week/05_memory_move.pdf new file mode 100644 index 00000000..4bb2c3b5 Binary files /dev/null and b/05_week/05_memory_move.pdf differ diff --git a/05_week/CMakeLists.txt b/05_week/CMakeLists.txt new file mode 100644 index 00000000..cdf912be --- /dev/null +++ b/05_week/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.14) # Минимальная требуемая версия CMake +project(05_week) # Необходим для инициализации cmake + +set(CMAKE_CXX_STANDARD 20) # Версия стандарта C++ +set(CMAKE_CXX_STANDARD_REQUIRED ON) # Гарантирует использование указанной версии стандарта + +set(EXAMPLES_DIR examples) # Определим переменную с именем директории +set(TASKS_DIR tasks) + +add_subdirectory(tasks) + +# Создать исполняемый файл для каждого примера +if (BUILD_EXAMPLES_05_WEEK) + +endif() \ No newline at end of file diff --git a/05_week/examples/storage_duration.cpp b/05_week/examples/storage_duration.cpp new file mode 100644 index 00000000..38079f4a --- /dev/null +++ b/05_week/examples/storage_duration.cpp @@ -0,0 +1,127 @@ +#include +#include + +int global_zero; // bss +int global_init = 18; // data +const int global_const = 10; // rodata + +int global_zero_2; // bss +int global_init_2 = 18; // data +const int global_const_2 = 10; // rodata + +static int static_zero; +static int static_init = 18; + +const char* str_a = "hello"; +const char* str_b = "hello"; + +thread_local int tlocal_var = 10; + +void local_var(int func_arg, const int func_arg_const, int& ref, const int& ref_const) { + std::cout << __func__ << ':' << std::endl; + int local = 18; + const int local_const = 10; + std::cout << "addr func_arg = \t" << &func_arg << std::endl; + std::cout << "addr func_arg_const = \t" << &func_arg_const << std::endl; + std::cout << "addr ref = \t\t" << &ref << std::endl; + std::cout << "addr ref_const = \t" << &ref_const << std::endl; + std::cout << "addr local = \t\t" << &local << std::endl; + std::cout << "addr local_const = \t" << &local_const << std::endl; + const int local_const_2 = 20; + int local_two = 2; + std::cout << "addr local_const_2 = \t" << &local_const_2 << std::endl; + std::cout << "addr local_two = \t" << &local_two << std::endl; + std::cout << std::endl; +} + +void same_literals() { + std::cout << __func__ << ':' << std::endl; + const char* str_c = "hello"; + + std::cout << "addr str_a = \t" << &str_a << std::endl; + std::cout << "addr str_b = \t" << &str_b << std::endl; + std::cout << "addr str_c = \t" << &str_c << std::endl; + std::cout << std::endl; +} + +class Static { +public: + static int* get_static_class_var() { + return &static_class_var; + } +private: + static int static_class_var; +}; + +int Static::static_class_var = 0; + +void static_var() { + std::cout << __func__ << ':' << std::endl; + static int static_local_var = 10; + + std::cout << "addr static_zero = \t\t" << &static_zero << std::endl; + std::cout << "addr static_init = \t\t" << &static_init << std::endl; + std::cout << "addr static_local_var = \t" << &static_local_var << std::endl; + std::cout << "addr static_class_var = \t" << Static::get_static_class_var() << std::endl; + std::cout << std::endl; +} + +void dynamic_var() { + std::cout << __func__ << ':' << std::endl; + + int* dynamic_var = new int; + *dynamic_var = 10; + int* dynamic_var_init = new int(10); + int* dynamic_arr = new int[10]{}; + int* dynamic_var_init2 = new int{2}; + std::cout << "addr dynamic_var = \t" << dynamic_var << std::endl; + std::cout << "addr dynamic_var_init = " << dynamic_var_init << std::endl; + std::cout << "addr dynamic_arr = \t" << dynamic_arr << std::endl; + std::cout << "addr dynamic_var_init2 = " << dynamic_var_init2 << std::endl; + delete dynamic_var; + delete dynamic_var_init; + delete[] dynamic_arr; + delete dynamic_var_init2; + std::cout << std::endl; +} + +void thread_var() { + std::cout << __func__ << ':' << std::endl; + int local_var; + std::cout << "addr tlocal_var = \t" << &tlocal_var << std::endl; + std::cout << "addr global_zero = \t" << &global_zero << std::endl; + std::cout << "addr local_var = \t" << &local_var << std::endl; + std::cout << std::endl; +} + +int main() { + std::cout << "global var:" << std::endl; + std::cout << "addr global_zero = " << &global_zero << std::endl; + std::cout << "addr global_zero_2 = " << &global_zero_2 << std::endl; + std::cout << "addr global_init = " << &global_init << std::endl; + std::cout << "addr global_init_2 = " << &global_init_2 << std::endl; + std::cout << "addr global_const = " << &global_const << std::endl; + std::cout << "addr global_const_2 = " << &global_const_2 << std::endl; + std::cout << std::endl; + + std::cout << "func:" << std::endl; + std::cout << "addr func local_var = " << (void*)&local_var << std::endl; + std::cout << "addr func main = " << (void*)&main << std::endl; + std::cout << "addr func static class = " << (void*)&Static::get_static_class_var << std::endl; + std::cout << std::endl; + + std::cout << "local var main:" << std::endl; + int main_var = 0; + const int main_var_const = 0; + std::cout << "addr main_var = " << &main_var << std::endl; + std::cout << "addr main_var_const = " << &main_var_const << std::endl; + std::cout << std::endl; + + local_var(main_var, main_var_const, main_var, main_var_const); + same_literals(); + static_var(); + dynamic_var(); + thread_var(); +} + + diff --git a/05_week/tasks/CMakeLists.txt b/05_week/tasks/CMakeLists.txt new file mode 100644 index 00000000..c06cbe6e --- /dev/null +++ b/05_week/tasks/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(tracer) +add_subdirectory(string_view) +add_subdirectory(cow_string) +add_subdirectory(simple_vector) \ No newline at end of file diff --git a/05_week/tasks/cow_string/CMakeLists.txt b/05_week/tasks/cow_string/CMakeLists.txt new file mode 100644 index 00000000..d234c642 --- /dev/null +++ b/05_week/tasks/cow_string/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_cow_string test.cpp) \ No newline at end of file diff --git a/05_week/tasks/cow_string/README.md b/05_week/tasks/cow_string/README.md new file mode 100644 index 00000000..edb1a1cd --- /dev/null +++ b/05_week/tasks/cow_string/README.md @@ -0,0 +1,74 @@ +[cow]: https://en.wikipedia.org/wiki/Copy-on-write + +# Коровья строка + +Существуют разные подходы к управлению данными при копировании. Можно выделить +следующие семантики: + +- value-семантика - при копировании объекта происходит глубокое копирование (deep copy), + все значения полностью объекта полностью копируются (реализовано в большинстве классов + стандартной библиотеки C++). +- reference-семантика - при копировании объекта его содержимое не копируется, + а разделяется между копиями посредством ссылки или указателя. Также существует подход, + который называют поверхностное копирование (shallow copy), когда происходит + копирование только верхнего уровня оригинального объекта, а ссылки на внутренние + части объекта остаются общими (реализовано по умолчанию в некоторых других языках). +- [cow]-семантика (copy-on-write) - при копировании объекта создается shallow copy, + что позволяет использовать объект на чтение без дополнительных затрат ресурсов, но + при изменении объекта создается настоящая глубокая копия и изменения вносятся уже в копию. + Таким образом, копирование совершается только при внесении изменений, что в + определенных сценариях использования объекта позволяет увеличить производительность + и уменьшить затраты на ресурсы. + +Cow-семантика применяется в реализации строк Яндекса и фреймворка Qt. + +Необходимо реализовать класс `CowString`, который представляет собой упрощенную +реализацию строки с copy-on-write семантикой. + +Класс предоставляет следующий функционал: + +- Конструктор по умолчанию — создает пустую строку +- Конструктор от `const char*` +- Конструктор от `std::string` +- Конструктор копирования — увеличивает счетчик ссылок, не копирует данные +- Оператор присваивания копированием — увеличивает счетчик ссылок, не копирует данные +- Конструктор перемещения +- Оператор присваивания перемещением +- Деструктор + +Методы **НЕ** вызывающие копирования: + +- Метод `Size` - возвращает длину строки без учета терминирующего нуля `\0` +- Метод `ToCstr` - возвращает указатель на данные +- Метод `ToString` - возвращает `std::string` +- Оператор `[]` - доступ к символу для чтения +- Оператор неявного преобразования к C-строке + +Методы, обеспечивающие модификацию на собственной копии данных: + +- Оператор `[]` - доступ к символу для записи +- Метод `Append` - добавляет строку из C-строки или `std::string` +- Метод `Substr` - принимает позицию и количество символов (по умолчанию от начала + до конца строки), возвращает соответствующую подстроку. Если позиция начала + превышает длину, то возвращаем пустую строку. +- Метод `Clear` - очистка строки + +Для реализации строки удобно вынести все необходимые поля в отдельную структуру +и кроме этого хранить в ней счетчик ссылок `ref_count`. + +Правильно поддерживая `ref_count` всегда будет известно, когда нужно удалять +данные строки или когда нужно превращать shallow-copy в deep-copy. + +## Примечание + +- **Запрещено** использовать `std::string` в реализации и умные указатели +- Рекомендуется определять методы вне класса +- При необходимости вспомогательные методы реализуются в закрытой части класса +- Тесты оператор `[]` не проверяют на индекс вне диапазона + +## Проблемы реализации + +- В предполагаемой реализации имеется следующая проблема: при использовании + модифицирующей версии оператора `[]` пользователь может сохранить ссылку + и модифицировать символ позже, что нарушит cow-семантику. Для упрощения + решать её не требуется. В Qt используют `QCharRef` для решения этой проблемы. diff --git a/05_week/tasks/cow_string/cow_string.cpp b/05_week/tasks/cow_string/cow_string.cpp new file mode 100644 index 00000000..34d59738 --- /dev/null +++ b/05_week/tasks/cow_string/cow_string.cpp @@ -0,0 +1,6 @@ +#include +#include + +class CowString { + +}; diff --git a/05_week/tasks/cow_string/test.cpp b/05_week/tasks/cow_string/test.cpp new file mode 100644 index 00000000..bf54b279 --- /dev/null +++ b/05_week/tasks/cow_string/test.cpp @@ -0,0 +1,519 @@ +#include + +#include "cow_string.cpp" + +TEST(CowStringTest, DefaultConstructor) { + CowString s; + EXPECT_EQ(s.Size(), 0); + EXPECT_STREQ(s.ToCstr(), ""); + EXPECT_TRUE(s.ToString().empty()); +} + +TEST(CowStringTest, ConstructorFromCString) { + CowString s("Hello"); + EXPECT_EQ(s.Size(), 5); + EXPECT_STREQ(s.ToCstr(), "Hello"); + EXPECT_EQ(s.ToString(), "Hello"); +} + +TEST(CowStringTest, ConstructorFromStdString) { + std::string str = "World"; + CowString s(str); + EXPECT_EQ(s.Size(), 5); + EXPECT_STREQ(s.ToCstr(), "World"); + EXPECT_EQ(s.ToString(), "World"); +} + +TEST(CowStringTest, ConstructorFromEmptyCString) { + CowString s(""); + EXPECT_EQ(s.Size(), 0); + EXPECT_STREQ(s.ToCstr(), ""); + EXPECT_TRUE(s.ToString().empty()); +} + +TEST(CowStringTest, CopyConstructorCOWSemantics) { + CowString s1("Hello"); + CowString s2 = s1; + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, MoveConstructor) { + CowString s1("Hello"); + const char* original_ptr = s1.ToCstr(); + + CowString s2 = std::move(s1); + + EXPECT_EQ(s2.ToCstr(), original_ptr); + EXPECT_STREQ(s2.ToCstr(), "Hello"); + + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s1.ToCstr(), ""); +} + +TEST(CowStringTest, CopyAssignmentCOWSemantics) { + CowString s1("Hello"); + CowString s2; + + s2 = s1; + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, CopyAssignment_SelfAssignment) { + CowString s("Hello"); + const char* original_ptr = s.ToCstr(); + + s = s; + + EXPECT_EQ(s.ToCstr(), original_ptr); + EXPECT_STREQ(s.ToCstr(), "Hello"); +} + +TEST(CowStringTest, MoveAssignment) { + CowString s1("Hello"); + CowString s2; + + const char* original_ptr = s1.ToCstr(); + + s2 = std::move(s1); + + EXPECT_EQ(s2.ToCstr(), original_ptr); + EXPECT_STREQ(s2.ToCstr(), "Hello"); + + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s1.ToCstr(), ""); +} + +TEST(CowStringTest, ReadOperationsNoCopy) { + CowString s1("Hello World"); + CowString s2 = s1; + + const char* original_ptr = s1.ToCstr(); + + size_t size1 = s1.Size(); + size_t size2 = s2.Size(); + + const char* cstr1 = s1.ToCstr(); + const char* cstr2 = s2.ToCstr(); + + std::string str1 = s1.ToString(); + std::string str2 = s2.ToString(); + + const CowString& s1_cref = s1; + const CowString& s2_cref = s2; + char ch1 = s1_cref[0]; + char ch2 = s2_cref[0]; + + EXPECT_EQ(s1.ToCstr(), original_ptr); + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + EXPECT_EQ(size1, 11); + EXPECT_EQ(size2, 11); + EXPECT_STREQ(cstr1, "Hello World"); + EXPECT_STREQ(cstr2, "Hello World"); + EXPECT_EQ(str1, "Hello World"); + EXPECT_EQ(str2, "Hello World"); + EXPECT_EQ(ch1, 'H'); + EXPECT_EQ(ch2, 'H'); +} + +TEST(CowStringTest, WriteOperationCausesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr1_before = s1.ToCstr(); + const char* ptr2_before = s2.ToCstr(); + + EXPECT_EQ(ptr1_before, ptr2_before); + + s2[0] = 'J'; + + const char* ptr1_after = s1.ToCstr(); + const char* ptr2_after = s2.ToCstr(); + + EXPECT_NE(ptr1_after, ptr2_after); + EXPECT_EQ(ptr1_before, ptr1_after); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); + EXPECT_STREQ(s2.ToCstr(), "Jello"); +} + +TEST(CowStringTest, WriteOperationMultipleCopies) { + CowString original("Hello"); + CowString copy1 = original; + CowString copy2 = original; + CowString copy3 = original; + + EXPECT_EQ(original.ToCstr(), copy1.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy2.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy3.ToCstr()); + + copy2[1] = 'a'; + + EXPECT_NE(original.ToCstr(), copy2.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy1.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy3.ToCstr()); + + EXPECT_STREQ(original.ToCstr(), "Hello"); + EXPECT_STREQ(copy1.ToCstr(), "Hello"); + EXPECT_STREQ(copy2.ToCstr(), "Hallo"); + EXPECT_STREQ(copy3.ToCstr(), "Hello"); +} + +TEST(CowStringTest, AppendFromCStringCausesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr1_before = s1.ToCstr(); + const char* ptr2_before = s2.ToCstr(); + EXPECT_EQ(ptr1_before, ptr2_before); + + s1.Append(" World"); + + const char* ptr1_after = s1.ToCstr(); + const char* ptr2_after = s2.ToCstr(); + + EXPECT_NE(ptr1_after, ptr2_after); + EXPECT_EQ(ptr2_before, ptr2_after); + + EXPECT_STREQ(s1.ToCstr(), "Hello World"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, AppendFromStdString) { + CowString s("Hello"); + std::string world = " World"; + + s.Append(world); + + EXPECT_STREQ(s.ToCstr(), "Hello World"); + EXPECT_EQ(s.Size(), 11); +} + +TEST(CowStringTest, AppendToEmptyString) { + CowString s; + s.Append("Hello"); + + EXPECT_STREQ(s.ToCstr(), "Hello"); + EXPECT_EQ(s.Size(), 5); +} + +TEST(CowStringTest, AppendMultipleTimes) { + CowString s("Hello"); + + s.Append(", "); + s.Append("World"); + s.Append("!"); + + EXPECT_STREQ(s.ToCstr(), "Hello, World!"); + EXPECT_EQ(s.Size(), 13); +} + +TEST(CowStringTest, AppendChainedMultipleTimes) { + CowString s("Hello"); + + s.Append(", ").Append("World").Append("!"); + + EXPECT_STREQ(s.ToCstr(), "Hello, World!"); + EXPECT_EQ(s.Size(), 13); +} + +TEST(CowStringTest, AppendEmptyStringNoCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* original_ptr = s1.ToCstr(); + + s1.Append(""); + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s1.ToCstr(), original_ptr); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); +} + +TEST(CowStringTest, SubstrDoesNotModifyOriginal) { + CowString s("Hello World"); + CowString copy = s; + + CowString sub = s.Substr(0, 5); + + EXPECT_EQ(s.ToCstr(), copy.ToCstr()); + EXPECT_NE(s.ToCstr(), sub.ToCstr()); + + EXPECT_STREQ(s.ToCstr(), "Hello World"); + EXPECT_STREQ(sub.ToCstr(), "Hello"); +} + +TEST(CowStringTest, SubstrDefaultParameters) { + CowString s("Hello World"); + + CowString sub1 = s.Substr(); + EXPECT_STREQ(sub1.ToCstr(), "Hello World"); + + CowString sub2 = s.Substr(6); + EXPECT_STREQ(sub2.ToCstr(), "World"); + + CowString sub3 = s.Substr(0, 5); + EXPECT_STREQ(sub3.ToCstr(), "Hello"); + + CowString sub4 = s.Substr(0, CowString::npos); + EXPECT_STREQ(sub4.ToCstr(), "Hello World"); +} + +TEST(CowStringTest, Substr_FullString) { + CowString s("Hello World"); + + CowString sub = s.Substr(0, s.Size()); + + EXPECT_STREQ(sub.ToCstr(), "Hello World"); + EXPECT_EQ(sub.Size(), 11); +} + +TEST(CowStringTest, Substr_Partial) { + CowString s("Hello World"); + + CowString sub = s.Substr(6, 5); + + EXPECT_STREQ(sub.ToCstr(), "World"); + EXPECT_EQ(sub.Size(), 5); +} + +TEST(CowStringTest, SubstrCountGreaterThanAvailable) { + CowString s("Hello"); + + CowString sub = s.Substr(2, 10); + + EXPECT_STREQ(sub.ToCstr(), "llo"); + EXPECT_EQ(sub.Size(), 3); +} + +TEST(CowStringTest, SubstrFromEmptyString) { + CowString se; + se.Clear(); + + CowString sub1 = se.Substr(0, CowString::npos); + EXPECT_STREQ(sub1.ToCstr(), ""); + EXPECT_EQ(sub1.Size(), 0); + + CowString sub2 = se.Substr(0, 5); + EXPECT_STREQ(sub2.ToCstr(), ""); + EXPECT_EQ(sub2.Size(), 0); +} + +TEST(CowStringTest, SubstrInvalidPos) { + CowString s("Hello"); + + auto sub1 = s.Substr(10); + EXPECT_STREQ(sub1.ToCstr(), ""); + EXPECT_EQ(sub1.Size(), 0); + + auto sub2 = s.Substr(6, CowString::npos); + EXPECT_STREQ(sub2.ToCstr(), ""); + EXPECT_EQ(sub2.Size(), 0); +} + +TEST(CowStringTest, ClearCreatesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr2_before = s2.ToCstr(); + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + s1.Clear(); + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s2.ToCstr(), ptr2_before); + + EXPECT_STREQ(s1.ToCstr(), ""); + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s2.ToCstr(), "Hello"); + EXPECT_EQ(s2.Size(), 5); +} + +TEST(CowStringTest, ClearEmptyString) { + CowString s; + CowString copy = s; + + s.Clear(); + + EXPECT_STREQ(s.ToCstr(), ""); + EXPECT_EQ(s.Size(), 0); + EXPECT_STREQ(copy.ToCstr(), ""); + EXPECT_EQ(copy.Size(), 0); +} + +TEST(CowStringTest, OperatorConstCharStar) { + CowString s("Hello"); + + const char* cstr = s; + + EXPECT_STREQ(cstr, "Hello"); +} + +TEST(CowStringTest, IndexOperatorRead) { + CowString s("Hello"); + + const CowString& s_cref = s; + EXPECT_EQ(s_cref[0], 'H'); + EXPECT_EQ(s_cref[1], 'e'); + EXPECT_EQ(s_cref[4], 'o'); +} + +TEST(CowStringTest, IndexOperatorWriteCausesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr1_before = s1.ToCstr(); + const char* ptr2_before = s2.ToCstr(); + EXPECT_EQ(ptr1_before, ptr2_before); + + s1[0] = 'J'; + + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s2.ToCstr(), ptr2_before); + + EXPECT_STREQ(s1.ToCstr(), "Jello"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, ImplicitConversionInComparisons) { + CowString s("Hello"); + + EXPECT_STREQ(s, "Hello"); + EXPECT_STRNE(s, "World"); +} + +TEST(CowStringTest, MultipleOperations) { + CowString s1("Hello"); + CowString s2 = s1; + CowString s3 = s2; + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s2.ToCstr(), s3.ToCstr()); + + s2.Append(" World"); + + EXPECT_EQ(s1.ToCstr(), s3.ToCstr()); + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + + s1[0] = 'J'; + + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + EXPECT_NE(s1.ToCstr(), s3.ToCstr()); + EXPECT_NE(s2.ToCstr(), s3.ToCstr()); + + EXPECT_STREQ(s1.ToCstr(), "Jello"); + EXPECT_STREQ(s2.ToCstr(), "Hello World"); + EXPECT_STREQ(s3.ToCstr(), "Hello"); +} + +TEST(CowStringTest, MemoryManagementDestructor) { + CowString* s1 = new CowString("Hello"); + CowString* s2 = new CowString(*s1); + + const char* s1_ptr = s1->ToCstr(); + const char* s2_ptr = s2->ToCstr(); + + EXPECT_EQ(s1_ptr, s2_ptr); + + delete s1; + + EXPECT_STREQ(s2->ToCstr(), "Hello"); + + delete s2; +} + +TEST(CowStringTest, AssignmentChain) { + CowString a("A"); + CowString b("B"); + CowString c("C"); + + a = b = c; + + EXPECT_EQ(a.ToCstr(), b.ToCstr()); + EXPECT_EQ(b.ToCstr(), c.ToCstr()); + EXPECT_EQ(a.ToCstr(), c.ToCstr()); + + EXPECT_STREQ(a.ToCstr(), "C"); + EXPECT_STREQ(b.ToCstr(), "C"); + EXPECT_STREQ(c.ToCstr(), "C"); +} + +TEST(CowStringTest, LargeStringCOWBenefits) { + + std::string large(10000, 'A'); + CowString s1(large); + + std::vector copies(10); + for (auto& copy : copies) { + copy = s1; + } + + for (const auto& copy : copies) { + EXPECT_EQ(s1.ToCstr(), copy.ToCstr()); + } + + copies[5][0] = 'B'; + + EXPECT_NE(s1.ToCstr(), copies[5].ToCstr()); + + for (size_t i = 0; i < copies.size(); ++i) { + if (i != 5) { + EXPECT_EQ(s1.ToCstr(), copies[i].ToCstr()); + } + } + + EXPECT_EQ(s1.ToCstr()[0], 'A'); + EXPECT_EQ(copies[5].ToCstr()[0], 'B'); + + for (size_t i = 1; i < 100; ++i) { + EXPECT_EQ(s1.ToCstr()[i], 'A'); + EXPECT_EQ(copies[5].ToCstr()[i], 'A'); + } +} + +TEST(CowStringTest, EmptyStringsShareData) { + CowString s1; + CowString s2; + + EXPECT_STREQ(s1.ToCstr(), ""); + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s2.ToCstr(), ""); + EXPECT_EQ(s2.Size(), 0); + + CowString s3 = s1; + EXPECT_STREQ(s3.ToCstr(), ""); + EXPECT_EQ(s3.Size(), 0); +} + +TEST(CowStringTest, FindMethod) { + CowString s("Hello World"); + + EXPECT_EQ(s.Find("World"), 6); + EXPECT_EQ(s.Find("Hello"), 0); + EXPECT_EQ(s.Find("Universe"), CowString::npos); + EXPECT_EQ(s.Find('o'), 4); + EXPECT_EQ(s.Find('x'), CowString::npos); + EXPECT_EQ(s.Find(""), 0); +} + +TEST(CowStringTest, EmptyMethod) { + CowString s1("Hello"); + EXPECT_FALSE(s1.Empty()); + + CowString s2; + EXPECT_TRUE(s2.Empty()); + + CowString s3(""); + EXPECT_TRUE(s3.Empty()); + + s1.Clear(); + EXPECT_TRUE(s1.Empty()); +} diff --git a/05_week/tasks/simple_vector/CMakeLists.txt b/05_week/tasks/simple_vector/CMakeLists.txt new file mode 100644 index 00000000..44ad31db --- /dev/null +++ b/05_week/tasks/simple_vector/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_simple_vector test.cpp) \ No newline at end of file diff --git a/05_week/tasks/simple_vector/README.md b/05_week/tasks/simple_vector/README.md new file mode 100644 index 00000000..6b7a4c47 --- /dev/null +++ b/05_week/tasks/simple_vector/README.md @@ -0,0 +1,66 @@ +# Вектор чисел + +Необходимо реализовать класс `SimpleVector`, представляющий упрощенную реализацию +контейнера `std::vector` для целочисленных элементов типа `int` в динамической +памяти. + +Класс предоставляет следующий функционал: + +- Конструктор по умолчанию +- Конструктор, принимающий размер вектора и заполняющий его нулями +- Конструктор, принимающий список инициализации `std::initializer_list`, + что позволит писать `SimpleVector v = {1, 3, 5}` +- Конструктор копирования +- Конструктор перемещения +- Операторы присваивания копированием +- Оператор присваивания перемещением +- Деструктор +- Метод `Swap` - принимает другой вектор и меняет содержимое текущего вектора с ним местами +- Операторы индексирования `[]`- позволяет изменять содержимое для неконстантного вектора +- Метод `Size` - возвращает число элементов в векторе +- Метод `Capacity` - возвращает текущее число выделенных ячеек памяти под вектор. +- Метод `Empty` - возвращает `true`, если вектор пуст +- Метод `Data` - прямой доступ к памяти, не позволяющий вносить изменения +- Метод `PushBack` который вставляет элемент в конец вектора. +- Метод `PopBack` - удаляет последний элемент вектора. При этом изменяется только размер, + выделенную память изменять не нужно. +- Метод `Insert` - принимает позицию (или `const int*`) и элемент, вставляет элемент перед + указанной позицией, возвращает указатель на вставленный элемент, позволяющий вносить изменения. + При вставке элементы контейнера, начиная с указанной позиции до конца вектора смещаются + на одну позицию. Если передана некорректная позиция, вставка не происходит, возвращается + указатель за последний элемент контейнера. +- Метод `Erase` - принимает позицию (или `const int*`), удаляет элемент в указанной позиции + и возвращает указатель на элемент, который был следующим за удаляемым. + Элементы после указанной позиции сдвигаются. Если передана некорректная позиция, удаление + не производится. +- Метод `Clear` - очистить вектор. При этом, как правило, изменяется только размер, память + очищать не нужно и выполнять релокации тоже. +- Метод `Resize` - принимает размер и значение (по умолчанию 0), изменяет размер массива на + заданный. Если переданный размер совпадает с текущим, то без изменений. Если меньше, то + изменяет размер. Если больше, то при необходимости производит релокацию и заполняет элементы + заданным значением +- Метод `Reserve` - принимает новое значение вместимости, позволяя зарезервировать место в + векторе. Если текущий `Capacity` не меньше переданного, то метод не должен ничего делать. + В противном случае выполните релокацию в массив размера заданной вместимости. +- Поддержка работы range-based for для контейнера. В данном случае для простоты + допустимо возвращать указатели на первый элемент и за последний, концепция итераторов + будет обсуждаться позже. +- Операторы сравнения на равенство и неравенство, учитывающие размер и поэлементное + сравнение элементов + +При добавлении элемента, если память, выделенная для вектора, заполнена, то выполните +релокацию: выделите массив вдвое большего размера, скопируйте элементы туда, +после чего удалите старый массив. В этом случае вместимость должна увеличиться вдвое. +Для вектора, сконструированного с помощью конструктора по умолчанию, вместимость == 0, +при добавлении элемента вместимость становится 1. + +## Примечание + +- **Запрещено** использовать стандартные контейнеры (`std::vector`, умные указатели) +- Устройство вектора обсуждалось в конце третьей лекции +- Для поддержки range-based for необходимы методы `begin`, `end` или внешние функции + `begin`, `end`, принимающие заданную коллекцию, поэтому допустимо, чтобы они не + соответствовали стайлгайду. Если стайлгайд не хочется +- Для совместимости с алгоритмами стандартной библиотеки **STL** может потребоваться + `swap`, ситуация аналогичная, но поскольку требуется внутри класса `Swap`, достаточно + реализовать внешнюю функцию, вызывающую метод `Swap` контейнера diff --git a/05_week/tasks/simple_vector/simple_vector.cpp b/05_week/tasks/simple_vector/simple_vector.cpp new file mode 100644 index 00000000..9b2ea971 --- /dev/null +++ b/05_week/tasks/simple_vector/simple_vector.cpp @@ -0,0 +1,5 @@ + + +class SimpleVector { + +}; \ No newline at end of file diff --git a/05_week/tasks/simple_vector/test.cpp b/05_week/tasks/simple_vector/test.cpp new file mode 100644 index 00000000..9bf16ed4 --- /dev/null +++ b/05_week/tasks/simple_vector/test.cpp @@ -0,0 +1,397 @@ +#include + +#include "simple_vector.cpp" + + +TEST(SimpleVectorTest, DefaultConstructor) { + SimpleVector v; + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 0); + EXPECT_TRUE(v.Empty()); +} + +TEST(SimpleVectorTest, SizeConstructor) { + SimpleVector v(5); + EXPECT_EQ(v.Size(), 5); + EXPECT_EQ(v.Capacity(), 5); + EXPECT_FALSE(v.Empty()); + + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], 0); + } +} + +TEST(SimpleVectorTest, SizeValueConstructor) { + SimpleVector v(3, 42); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v.Capacity(), 3); + + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], 42); + } +} + +TEST(SimpleVectorTest, InitializerListConstructor) { + SimpleVector v = {1, 2, 3, 4, 5}; + EXPECT_EQ(v.Size(), 5); + EXPECT_EQ(v.Capacity(), 5); + + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i + 1)); + } +} + +TEST(SimpleVectorTest, CopyConstructor) { + SimpleVector original = {1, 2, 3}; + SimpleVector copy(original); + + EXPECT_EQ(copy.Size(), original.Size()); + EXPECT_EQ(copy.Capacity(), original.Capacity()); + + EXPECT_EQ(original, copy); + + copy[0] = 100; + EXPECT_EQ(original[0], 1); +} + +TEST(SimpleVectorTest, MoveConstructor) { + SimpleVector original = {1, 2, 3}; + size_t original_size = original.Size(); + size_t original_capacity = original.Capacity(); + const int* original_data = original.Data(); + + SimpleVector moved(std::move(original)); + + EXPECT_EQ(moved.Size(), original_size); + EXPECT_EQ(moved.Capacity(), original_capacity); + EXPECT_EQ(moved.Data(), original_data); + + EXPECT_EQ(original.Size(), 0); + EXPECT_EQ(original.Capacity(), 0); + EXPECT_EQ(original.Data(), nullptr); +} + + +TEST(SimpleVectorTest, CopyAssignment) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {4, 5}; + + v2 = v1; + + EXPECT_EQ(v2.Size(), v1.Size()); + EXPECT_EQ(v2.Capacity(), v1.Size()); + + EXPECT_EQ(v2, v1); +} + +TEST(SimpleVectorTest, SelfCopyAssignment) { + SimpleVector v1 = {1, 2, 3}; + auto cap = v1.Capacity(); + + v1 = v1; + + EXPECT_EQ(v1.Capacity(), cap); + EXPECT_EQ(v1.Size(), 3); + EXPECT_EQ(v1[0], 1); +} +TEST(SimpleVectorTest, MoveAssignment) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2; + + const int* v1_data = v1.Data(); + size_t v1_size = v1.Size(); + size_t v1_capacity = v1.Capacity(); + + v2 = std::move(v1); + + EXPECT_EQ(v2.Size(), v1_size); + EXPECT_EQ(v2.Capacity(), v1_capacity); + EXPECT_EQ(v2.Data(), v1_data); + + EXPECT_EQ(v1.Size(), 0); + EXPECT_EQ(v1.Capacity(), 0); + EXPECT_EQ(v1.Data(), nullptr); +} + +TEST(SimpleVectorTest, PushBack) { + SimpleVector v; + + v.PushBack(1); + EXPECT_EQ(v.Size(), 1); + EXPECT_EQ(v.Capacity(), 1); + EXPECT_EQ(v[0], 1); + + v.PushBack(2); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(v.Capacity(), 2); + EXPECT_EQ(v[1], 2); + + v.PushBack(3); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v.Capacity(), 4); + EXPECT_EQ(v[2], 3); +} + +TEST(SimpleVectorTest, EmptyPopBack) { + SimpleVector v; + EXPECT_NO_THROW(v.PopBack()); + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 0); +} + +TEST(SimpleVectorTest, PopBack) { + SimpleVector v = {1, 2, 3, 4}; + + v.PopBack(); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v.Capacity(), 4); + + v.PopBack(); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v.Capacity(), 4); + + v.PopBack(); + v.PopBack(); + EXPECT_EQ(v.Size(), 0); + EXPECT_TRUE(v.Empty()); + EXPECT_EQ(v.Capacity(), 4); + + v.PopBack(); + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 4); +} + +TEST(SimpleVectorTest, Insert) { + SimpleVector v = {1, 3, 4}; + + int* inserted = v.Insert(v.Data() + 1, 2); + EXPECT_EQ(v.Size(), 4); + EXPECT_EQ(*inserted, 2); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i + 1)); + } + + v.Insert(v.Data(), 0); + EXPECT_EQ(v.Size(), 5); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i)); + } + + v.Insert(v.Data() + v.Size(), 5); + EXPECT_EQ(v.Size(), 6); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i)); + } + + int* result = v.Insert(v.Data() - 1, 100); + EXPECT_EQ(v.Size(), 6); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i)); + } + EXPECT_EQ(result, v.Data() + v.Size()); +} + +TEST(SimpleVectorTest, Erase) { + SimpleVector v = {1, 2, 3, 4, 5}; + + int* ptr = v.Erase(v.Data() + 1); + EXPECT_EQ(v.Size(), 4); + EXPECT_EQ(*ptr, 3); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 3); + EXPECT_EQ(v[2], 4); + EXPECT_EQ(v[3], 5); + + v.Erase(v.Data()); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v[0], 3); + + ptr = v.Erase(v.Data() + v.Size() - 1); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(ptr, v.Data() + v.Size()); + EXPECT_EQ(v[1], 4); + + ptr = v.Erase(v.Data() + v.Size() + 1); + EXPECT_EQ(ptr, v.Data() + v.Size()); +} + +TEST(SimpleVectorTest, Resize) { + SimpleVector v = {1, 2, 3}; + + v.Resize(2); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 2); + + v.Resize(4); + EXPECT_EQ(v.Size(), 4); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 2); + EXPECT_EQ(v[2], 0); + EXPECT_EQ(v[3], 0); + + v.Resize(6, 42); + EXPECT_EQ(v.Size(), 6); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 2); + EXPECT_EQ(v[2], 0); + EXPECT_EQ(v[3], 0); + EXPECT_EQ(v[4], 42); + EXPECT_EQ(v[5], 42); +} + +TEST(SimpleVectorTest, Reserve) { + SimpleVector v; + + v.Reserve(10); + EXPECT_EQ(v.Capacity(), 10); + EXPECT_EQ(v.Size(), 0); + + SimpleVector expected = {1, 2, 3}; + v = {1, 2, 3}; + v.Reserve(5); + EXPECT_EQ(v.Capacity(), 5); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v, expected); + + v.Reserve(2); + EXPECT_EQ(v.Capacity(), 5); +} + +TEST(SimpleVectorTest, Clear) { + SimpleVector v = {1, 2, 3, 4, 5}; + + v.Clear(); + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 5); + EXPECT_TRUE(v.Empty()); +} + +TEST(SimpleVectorTest, Swap) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {4, 5}; + + const int* v1_data = v1.Data(); + const int* v2_data = v2.Data(); + size_t v1_size = v1.Size(); + size_t v2_size = v2.Size(); + size_t v1_cap = v1.Capacity(); + size_t v2_cap = v2.Capacity(); + + v1.Swap(v2); + + EXPECT_EQ(v1.Size(), v2_size); + EXPECT_EQ(v1.Data(), v2_data); + EXPECT_EQ(v1.Capacity(), v2_cap); + EXPECT_EQ(v2.Size(), v1_size); + EXPECT_EQ(v2.Data(), v1_data); + EXPECT_EQ(v2.Capacity(), v1_cap); +} + +TEST(SimpleVectorTest, ExternalSwap) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {4, 5}; + + swap(v1, v2); + + EXPECT_EQ(v1.Size(), 2); + EXPECT_EQ(v2.Size(), 3); +} + +TEST(SimpleVectorTest, RangeBasedFor) { + SimpleVector v = {1, 2, 3, 4, 5}; + int sum = 0; + for (int x : v) { + sum += x; + } + EXPECT_EQ(sum, 15); + + const SimpleVector& cv = v; + sum = 0; + for (int x : cv) { + sum += x; + } + EXPECT_EQ(sum, 15); +} + +TEST(SimpleVectorTest, EqualityOperators) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {1, 2, 3}; + SimpleVector v3 = {1, 2}; + SimpleVector v4 = {1, 2, 4}; + + EXPECT_TRUE(v1 == v2); + EXPECT_FALSE(v1 != v2); + + EXPECT_FALSE(v1 == v3); + EXPECT_TRUE(v1 != v3); + + EXPECT_FALSE(v1 == v4); + EXPECT_TRUE(v1 != v4); +} + +TEST(SimpleVectorTest, AccessMethods) { + SimpleVector v = {10, 20, 30}; + const SimpleVector& cv = v; + + EXPECT_EQ(*v.Data(), 10); + EXPECT_EQ(*cv.Data(), 10); + + EXPECT_EQ(v[0], 10); + EXPECT_EQ(cv[1], 20); + + v[2] = 40; + EXPECT_EQ(v[2], 40); +} + +TEST(SimpleVectorTest, ComplexOperations) { + SimpleVector v; + + v.PushBack(1); + v.PushBack(2); + v.PushBack(3); + + v.Insert(v.Data() + 1, 99); + v.Erase(v.Data() + 2); + + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 99); + EXPECT_EQ(v[2], 3); + + v.Resize(5, 100); + EXPECT_EQ(v.Size(), 5); + EXPECT_EQ(v[3], 100); + EXPECT_EQ(v[4], 100); + + v.Reserve(20); + EXPECT_EQ(v.Capacity(), 20); +} + +TEST(SimpleVectorTest, WorkAfterMoveCtor) { + SimpleVector original = {1, 2, 3}; + SimpleVector moved(std::move(original)); + + moved.PushBack(17); + EXPECT_EQ(moved.Size(), 4); + EXPECT_EQ(moved[3], 17); + + original.PushBack(17); + EXPECT_EQ(original.Size(), 1); + EXPECT_EQ(original[0], 17); +} + +TEST(SimpleVectorTest, WorkAfterMoveAssignment) { + SimpleVector original = {1, 2, 3}; + SimpleVector moved = {4, 5}; + moved = std::move(original); + + moved.PushBack(17); + EXPECT_EQ(moved.Size(), 4); + EXPECT_EQ(moved[3], 17); + + original.PushBack(17); + EXPECT_EQ(original.Size(), 1); + EXPECT_EQ(original[0], 17); +} \ No newline at end of file diff --git a/05_week/tasks/string_view/CMakeLists.txt b/05_week/tasks/string_view/CMakeLists.txt new file mode 100644 index 00000000..2660fd76 --- /dev/null +++ b/05_week/tasks/string_view/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_string_view test.cpp) \ No newline at end of file diff --git a/05_week/tasks/string_view/README.md b/05_week/tasks/string_view/README.md new file mode 100644 index 00000000..660599cc --- /dev/null +++ b/05_week/tasks/string_view/README.md @@ -0,0 +1,42 @@ +[std::string_view]: https://en.cppreference.com/w/cpp/string/basic_string_view.html + +# Представление строки + +Необходимо реализовать класс `StringView`, представляющий упрощенную реализацию +[std::string_view]. В отличие от `std::string` данный класс не владеет строкой +(ресурсами), а хранит лишь размер и указатель на начало строки. Это позволяет +быстро создавать представление строки, копировать и создавать подстроки. Важно +понимать, что поскольку `StringView` не владеет строкой, а лишь наблюдает за +оригинальной строкой, то при изменении оригинала представление тоже изменится. +При этом сам класс `StringView` не может изменять строку, за которой наблюдает. + +Класс предоставляет следующий функционал: + +- Конструктор от `std::string`, позиции начала подстроки (по умолчанию 0) и длину + наблюдаемой подстроки (по умолчанию аналог `std::string::npos`). Длину подстроки + следует обрезать, если она превышает длину строки. Если начало превышает длину, + то следует создать `StringView` с параметрами по умолчанию +- Конструктор от C-строки +- Конструктор от C-строки и длины +- Оператор `[]` - доступ на чтение символа +- Метод `Data` - возвращает указатель на начало наблюдаемой строки +- Метод `Front` - доступ на чтение к первому символу. +- Метод `Back` - доступ на чтение к последнему символу. +- Методы `Size`, `Length` - возвращают длину наблюдаемой строки +- Метод `Empty` - проверяет на пустоту +- Метод `RemovePrefix` - убирает заданное количество символов из начала представления +- Метод `RemoveSuffix` - убирает заданное количество символов с конца представления +- Метод `Substr` - может принимать позицию начала поиска и количество элементов и + возвращает `StringView`. В случае, когда подстрока начала поиска превышает длину строки, + следует вернуть пустое представление +- Метод `Find` - принимает символ или `StringView` и позицию начала поиска (по умолчанию 0), + возвращает позицию начала совпадения элемента (или аналог `std::string::npos`) +- Метод `ToString` - создает `std::string` на основе представления + +## Примечание + +- **Запрещено** использовать `std::string_view` в реализации +- Передавать `std::string` по значению в первый конструктор очень плохая идея +- Получить длину C-строки можно с помощью функции `strlen`. +- Не забудьте про константность методов + diff --git a/05_week/tasks/string_view/string_view.cpp b/05_week/tasks/string_view/string_view.cpp new file mode 100644 index 00000000..438c4536 --- /dev/null +++ b/05_week/tasks/string_view/string_view.cpp @@ -0,0 +1,7 @@ +#include +#include + + +class StringView { + +}; \ No newline at end of file diff --git a/05_week/tasks/string_view/test.cpp b/05_week/tasks/string_view/test.cpp new file mode 100644 index 00000000..2388fbf0 --- /dev/null +++ b/05_week/tasks/string_view/test.cpp @@ -0,0 +1,349 @@ +#include + +#include "string_view.cpp" + +#include + +void ExpectEmpty(const StringView& sv) { + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); + EXPECT_EQ(sv.Length(), 0); + EXPECT_EQ(sv.Data(), nullptr); +} + +TEST(StringViewTest, DefaultConstructor) { + StringView sv; + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); + EXPECT_EQ(sv.Length(), 0); + EXPECT_EQ(sv.Data(), nullptr); +} + +TEST(StringViewTest, CtorStdString) { + std::string str = "Hello, World!"; + StringView sv(str); + + EXPECT_FALSE(sv.Empty()); + EXPECT_EQ(sv.Size(), str.size()); + EXPECT_EQ(sv.Length(), str.size()); + EXPECT_EQ(sv.Data(), str.data()); + EXPECT_EQ(sv.ToString(), str); +} + +TEST(StringViewTest, CtorStdStringWithPos) { + std::string str = "Hello, World!"; + + StringView sv(str, 7); + EXPECT_EQ(sv.Size(), 6); + EXPECT_EQ(sv.ToString(), "World!"); + + StringView sv2(str, 0); + EXPECT_EQ(sv2.ToString(), str); +} + +TEST(StringViewTest, CtorStdStringWithPosAndCount) { + std::string str = "Hello, World!"; + + StringView sv1(str, 0, 5); + EXPECT_EQ(sv1.ToString(), "Hello"); + + StringView sv2(str, 7, 5); + EXPECT_EQ(sv2.ToString(), "World"); + + StringView sv3(str, 7, 100); + EXPECT_EQ(sv3.ToString(), "World!"); + + StringView sv4(str, 7, StringView::npos); + EXPECT_EQ(sv4.ToString(), "World!"); +} + +TEST(StringViewTest, CtorStdStringWithInvalidPos) { + std::string str = "Hello"; + + EXPECT_NO_THROW(StringView(str, 10)); + EXPECT_NO_THROW(StringView(str, 6)); + + auto sv = StringView(str, 10); + ExpectEmpty(sv); +} + +TEST(StringViewTest, CtorCString) { + const char* cstr = "Hello, World!"; + StringView sv(cstr); + + EXPECT_FALSE(sv.Empty()); + EXPECT_EQ(sv.Size(), std::strlen(cstr)); + EXPECT_EQ(sv.ToString(), cstr); + EXPECT_EQ(sv.Data(), cstr); +} + +TEST(StringViewTest, CtorCStringNullptr) { + StringView sv(static_cast(nullptr)); + ExpectEmpty(sv); +} + +TEST(StringViewTest, CtorCStringAndLength) { + const char* cstr = "Hello, World!"; + + StringView sv1(cstr, 5); + EXPECT_EQ(sv1.Size(), 5); + EXPECT_EQ(sv1.ToString(), "Hello"); + + StringView sv2(cstr + 7, 5); + EXPECT_EQ(sv2.Size(), 5); + EXPECT_EQ(sv2.ToString(), "World"); +} + +TEST(StringViewTest, CtorCStringAndLengthNullptr) { + StringView sv1(nullptr, 0); + EXPECT_TRUE(sv1.Empty()); + + EXPECT_NO_THROW(StringView(nullptr, 5)); + auto sv = StringView(nullptr, 5); + ExpectEmpty(sv); +} + +TEST(StringViewTest, AccessMethods) { + std::string str = "Hello"; + StringView sv(str); + + EXPECT_EQ(sv[0], 'H'); + EXPECT_EQ(sv[1], 'e'); + EXPECT_EQ(sv[4], 'o'); + + EXPECT_EQ(sv.Front(), 'H'); + EXPECT_EQ(sv.Back(), 'o'); +} + +TEST(StringViewTest, RemovePrefix) { + std::string str = "Hello, World!"; + StringView sv(str); + + sv.RemovePrefix(7); + EXPECT_EQ(sv.ToString(), "World!"); + EXPECT_EQ(sv.Size(), 6); + + sv.RemovePrefix(5); + EXPECT_EQ(sv.ToString(), "!"); + EXPECT_EQ(sv.Size(), 1); + + sv.RemovePrefix(10); + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); +} + +TEST(StringViewTest, RemoveSuffix) { + std::string str = "Hello, World!"; + StringView sv(str); + + sv.RemoveSuffix(7); + EXPECT_EQ(sv.ToString(), "Hello,"); + EXPECT_EQ(sv.Size(), 6); + + sv.RemoveSuffix(5); + EXPECT_EQ(sv.ToString(), "H"); + EXPECT_EQ(sv.Size(), 1); + + sv.RemoveSuffix(10); + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); +} + +TEST(StringViewTest, Substr) { + std::string str = "Hello, World!"; + StringView sv(str); + + StringView sub1 = sv.Substr(0, 5); + EXPECT_EQ(sub1.ToString(), "Hello"); + + StringView sub2 = sv.Substr(7, 5); + EXPECT_EQ(sub2.ToString(), "World"); + + StringView sub3 = sv.Substr(7); + EXPECT_EQ(sub3.ToString(), "World!"); + + StringView sub4 = sv.Substr(7, 100); + EXPECT_EQ(sub4.ToString(), "World!"); + + StringView sub5 = sv.Substr(13, 5); + EXPECT_TRUE(sub5.Empty()); + EXPECT_EQ(sub5.ToString(), ""); +} + +TEST(StringViewTest, SubstrInvalidPos) { + std::string str = "Hello"; + StringView sv(str); + + auto substr = sv.Substr(6); + ExpectEmpty(substr); +} + +TEST(StringViewTest, FindChar) { + std::string str = "Hello, World!"; + StringView sv(str); + + EXPECT_EQ(sv.Find('H'), 0); + EXPECT_EQ(sv.Find('o'), 4); + EXPECT_EQ(sv.Find('!'), 12); + EXPECT_EQ(sv.Find('z'), StringView::npos); + + EXPECT_EQ(sv.Find('o', 5), 8); + EXPECT_EQ(sv.Find('l', 3), 3); + + EXPECT_EQ(sv.Find('H', 13), StringView::npos); +} + +TEST(StringViewTest, FindStringView) { + std::string str = "Hello, World! Hello again!"; + StringView sv(str); + + EXPECT_EQ(sv.Find(StringView("Hello")), 0); + EXPECT_EQ(sv.Find(StringView("World")), 7); + EXPECT_EQ(sv.Find(StringView("again")), 20); + EXPECT_EQ(sv.Find(StringView("nonexistent")), StringView::npos); + + EXPECT_EQ(sv.Find(StringView("")), 0); + + EXPECT_EQ(sv.Find(StringView("Hello"), 1), 14); + EXPECT_EQ(sv.Find(StringView("!"), 13), 25); + + EXPECT_EQ(sv.Find(StringView("Hello, World! Hello again! And more")), StringView::npos); +} + +TEST(StringViewTest, Find_EmptyView) { + StringView sv; + + EXPECT_EQ(sv.Find('a'), StringView::npos); + EXPECT_EQ(sv.Find(StringView("test")), StringView::npos); + + StringView empty_target(""); + EXPECT_EQ(sv.Find(empty_target), StringView::npos); +} + +TEST(StringViewTest, ToString) { + std::string str = "Hello, World!"; + StringView sv1(str); + EXPECT_EQ(sv1.ToString(), str); + + const char* cstr = "Test"; + StringView sv2(cstr); + EXPECT_EQ(sv2.ToString(), "Test"); + + StringView sv3 = sv1.Substr(7, 5); + EXPECT_EQ(sv3.ToString(), "World"); + + StringView sv4; + EXPECT_EQ(sv4.ToString(), ""); +} + +TEST(StringViewTest, ObservesOriginalString) { + std::string str = "Hello"; + StringView sv(str); + + const char* original_data = sv.Data(); + + str[0] = 'J'; + str.append(", World!"); + + EXPECT_EQ(sv.Data(), original_data); + EXPECT_EQ(sv.ToString(), "Jello"); +} + +TEST(StringViewTest, WorksWithStringLiterals) { + StringView sv1 = "Hello"; + EXPECT_EQ(sv1.ToString(), "Hello"); + + StringView sv2("World"); + EXPECT_EQ(sv2.ToString(), "World"); + + std::string result = sv1.ToString() + ", " + sv2.ToString(); + EXPECT_EQ(result, "Hello, World"); +} + +TEST(StringViewTest, NposConstant) { + EXPECT_EQ(StringView::npos, static_cast(-1)); + EXPECT_GT(StringView::npos, 1000000); // Очень большое число +} + +TEST(StringViewTest, IteratorLikeUsage) { + std::string str = "Hello"; + StringView sv(str); + + int count_l = 0; + for (size_t i = 0; i < sv.Size(); ++i) { + if (sv[i] == 'l') { + ++count_l; + } + } + EXPECT_EQ(count_l, 2); + + bool found = false; + for (size_t i = 0; i < sv.Size(); ++i) { + if (sv[i] == 'o') { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + +TEST(StringViewTest, MemorySafety) { + std::string str = "Hello"; + StringView sv1(str, 5); + EXPECT_TRUE(sv1.Empty()); + EXPECT_EQ(sv1.ToString(), ""); + + std::string temp("Temporary"); + StringView sv2(temp); + EXPECT_EQ(sv2.ToString(), "Temporary"); + + StringView sv3("Hello, World!"); + StringView sv4 = sv3.Substr(7); + StringView sv5 = sv4.Substr(0, 5); + EXPECT_EQ(sv5.ToString(), "World"); + + EXPECT_EQ(sv3.Data() + 7, sv4.Data()); + EXPECT_EQ(sv4.Data(), sv5.Data()); +} + +TEST(StringViewTest, ComplexScenario) { + std::string log_line = "[INFO] 2024-01-15 10:30:00 User 'admin' logged in from 192.168.1.1"; + StringView sv(log_line); + + size_t bracket_end = sv.Find(']'); + StringView level = sv.Substr(1, bracket_end - 1); + EXPECT_EQ(level.ToString(), "INFO"); + + size_t pos = bracket_end + 2; + size_t data_end = sv.Find(' ', pos); + StringView datetime = sv.Substr(pos, data_end - pos); + EXPECT_EQ(datetime.ToString(), "2024-01-15"); + + size_t user_start = sv.Find('\'', data_end); + size_t user_end = sv.Find('\'', user_start + 1); + StringView username = sv.Substr(user_start + 1, user_end - user_start - 1); + EXPECT_EQ(username.ToString(), "admin"); + + size_t ip_start = sv.Find("from") + 5; + StringView ip = sv.Substr(ip_start); + EXPECT_EQ(ip.ToString(), "192.168.1.1"); + + ip.RemovePrefix(4); + ip.RemoveSuffix(2); + EXPECT_EQ(ip.ToString(), "168.1"); +} + +TEST(StringViewTest, PerformanceCharacteristics) { + std::string large_string(1000000, 'A'); + StringView sv(large_string); + + EXPECT_EQ(sv.Size(), 1000000); + EXPECT_EQ(sv.Data(), large_string.data()); + + StringView substr = sv.Substr(500000, 1000); + EXPECT_EQ(substr.Size(), 1000); + EXPECT_EQ(substr.Data(), large_string.data() + 500000); + + std::string copy = sv.ToString(); + EXPECT_EQ(copy.size(), 1000000); +} \ No newline at end of file diff --git a/05_week/tasks/tracer/CMakeLists.txt b/05_week/tasks/tracer/CMakeLists.txt new file mode 100644 index 00000000..89739e1f --- /dev/null +++ b/05_week/tasks/tracer/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_tracer test.cpp) \ No newline at end of file diff --git a/05_week/tasks/tracer/README.md b/05_week/tasks/tracer/README.md new file mode 100644 index 00000000..a1809d93 --- /dev/null +++ b/05_week/tasks/tracer/README.md @@ -0,0 +1,43 @@ +# Трассировщик + +Необходимо реализовать класс `Tracer`, подсчитывающий вызовы своих конструкторов, +операторов присваивания и деструктора. + +В качестве данных внутри класса должна храниться имя `std::string` и идентификатор. + +В классе присутствуют следующие счетчики, доступные извне: + +- `count` - общее количество созданных объектов (используется для генерации id) +- `default_ctor` - количество вызовов конструктора по умолчанию +- `str_ctor` - количество вызовов конструктора от строки +- `copy_ctor` - количество вызовов конструктора копирования +- `move_ctor` - количество вызовов конструктора перемещения +- `copy_assign` - количество вызовов оператора копирующего присваивания +- `move_assign` - количество вызовов оператора перемещающего присваивания +- `dtor` - количество вызовов деструктора +- `alive` - количество живых объектов в данный момент + +Правила изменения идентификатора (id): + +- присваивается при создании и **НЕ** меняется в течение жизни объекта +- каждый новый объект получает уникальный id (увеличивающийся счетчик) +- при использовании операторов не изменяется + +Класс предоставляет следующий функционал: + +- Конструктор по умолчанию - создает объект с именем `obj_{id}` ("obj_1") +- Конструктор от строки `std::string` - создает объект с именем `{name}_{id}` +- Конструктор копирования - копирует имя, но создает id +- Конструктор перемещения - перемещает имя, но создает id +- Оператор присваивания копированием - копирует имя, не изменяет id +- Оператор перемещения копированием - перемещает имя, не изменяет id +- Деструктор - изменяет свои счетчики +- Метод `Id` - возвращает идентификатор объекта +- Метод `Name` - возвращает константную ссылку на имя. +- Метод `Data` - возвращает указатель на данные строки. +- Метод `ResetStats` - сбрасывает на `0` все счетчики + +## Примечание + +- Для удобства отладки можно написать функции, выводящие на экран статистики + и логирующие действия. diff --git a/05_week/tasks/tracer/test.cpp b/05_week/tasks/tracer/test.cpp new file mode 100644 index 00000000..4db2a9c8 --- /dev/null +++ b/05_week/tasks/tracer/test.cpp @@ -0,0 +1,370 @@ +#include + +#include + +#include "tracer.cpp" + + +class TracerTest : public ::testing::Test { +protected: + void SetUp() override { + Tracer::ResetStats(); + } +}; + +TEST_F(TracerTest, DefaultConstructor) { + { + Tracer obj; + EXPECT_EQ(obj.Id(), 1); + EXPECT_EQ(obj.Name(), "obj_1"); + EXPECT_NE(obj.Data(), nullptr); + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::default_ctor, 1); + EXPECT_EQ(Tracer::alive, 1); + } + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::default_ctor, 1); + EXPECT_EQ(Tracer::str_ctor, 0); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::dtor, 1); + EXPECT_EQ(Tracer::alive, 0); +} + +TEST_F(TracerTest, StringConstructor) { + { + Tracer obj("Hello"); + EXPECT_EQ(obj.Id(), 1); + EXPECT_EQ(obj.Name(), "Hello_1"); + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::alive, 1); + } + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::dtor, 1); + EXPECT_EQ(Tracer::alive, 0); +} + +TEST_F(TracerTest, CopyConstructor) { + Tracer original("Original"); + int original_id = original.Id(); + const char* original_ptr = original.Data(); + std::string original_name = original.Name(); + + Tracer copy = original; + + EXPECT_NE(original_id, copy.Id()); + EXPECT_EQ(copy.Id(), 2); + EXPECT_EQ(copy.Name(), original_name); + + EXPECT_NE(original_ptr, copy.Data()); + + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::copy_ctor, 1); + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, MoveConstructor) { + Tracer original("OriginalWithLongString"); + int original_id = original.Id(); + const char* original_ptr = original.Data(); + std::string original_name = original.Name(); + + Tracer moved = std::move(original); + + EXPECT_NE(original_id, moved.Id()); + EXPECT_EQ(moved.Id(), 2); + + EXPECT_EQ(moved.Name(), original_name); + EXPECT_TRUE(original.Name().empty()); + EXPECT_EQ(moved.Data(), original_ptr); + + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::move_ctor, 1); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, CopyAssignment) { + Tracer a("Apple"); + Tracer b("BananaBombaOuououou"); + + int a_id = a.Id(); + int b_id = b.Id(); + const char* a_ptr_before = a.Data(); + const char* b_ptr = b.Data(); + + a = b; + + EXPECT_EQ(a.Id(), a_id); + EXPECT_EQ(b.Id(), b_id); + EXPECT_EQ(a.Name(), b.Name()); + EXPECT_NE(a.Data(), b_ptr); + EXPECT_NE(a.Data(), a_ptr_before); + + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::copy_assign, 1); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, MoveAssignment) { + Tracer a("Apple"); + Tracer b("BananaBombaFlySoGreat"); + + int a_id = a.Id(); + int b_id = b.Id(); + const char* b_ptr_before = b.Data(); + std::string b_name = b.Name(); + + a = std::move(b); + + EXPECT_EQ(a.Id(), a_id); + EXPECT_EQ(b.Id(), b_id); + + EXPECT_EQ(a.Name(), b_name); + EXPECT_TRUE(b.Name().empty()); + EXPECT_EQ(a.Data(), b_ptr_before); + + EXPECT_EQ(Tracer::move_assign, 1); + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, SelfAssignmentCopy) { + Tracer obj("Test"); + std::string original_name = obj.Name(); + const char* original_ptr = obj.Data(); + int original_id = obj.Id(); + + obj = obj; + + EXPECT_EQ(obj.Id(), original_id); + EXPECT_EQ(obj.Name(), original_name); + EXPECT_EQ(obj.Data(), original_ptr); + + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, SelfAssignmentMove) { + Tracer obj("Test"); + std::string original_name = obj.Name(); + const char* original_ptr = obj.Data(); + int original_id = obj.Id(); + + obj = static_cast(obj); + + EXPECT_EQ(obj.Id(), original_id); + EXPECT_EQ(obj.Data(), original_ptr); + + EXPECT_EQ(Tracer::move_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, VectorPushBackCopy) { + std::vector vec; + vec.reserve(3); + + Tracer obj("Item"); + const char* obj_ptr = obj.Data(); + + vec.push_back(obj); + + EXPECT_EQ(vec[0].Name(), obj.Name()); + EXPECT_NE(vec[0].Data(), obj_ptr); + + EXPECT_EQ(Tracer::copy_ctor, 1); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, VectorPushBackMove) { + std::vector vec; + vec.reserve(3); + + Tracer obj("LongNameToAvoidSSO"); + const char* obj_ptr = obj.Data(); + std::string obj_name = obj.Name(); + + vec.push_back(std::move(obj)); + + EXPECT_EQ(vec[0].Data(), obj_ptr); + EXPECT_EQ(vec[0].Name(), obj_name); + EXPECT_TRUE(obj.Name().empty()); + + EXPECT_EQ(Tracer::move_ctor, 1); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, VectorEmplaceBack) { + std::vector vec; + vec.reserve(3); + + vec.emplace_back("Hello"); + + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::alive, 1); +} + +TEST_F(TracerTest, VectorReallocation) { + std::vector vec; + + vec.emplace_back("First"); // 1 ctor + vec.emplace_back("Second"); // 1 ctor + realloc (1 move) + vec.emplace_back("Third"); // 1 ctor + realloc (2 move) + + EXPECT_EQ(Tracer::str_ctor, 3); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 3); // 1 + 2 move when realloc + EXPECT_EQ(Tracer::alive, 3); +} + +TEST_F(TracerTest, VectorWithReserve) { + std::vector vec; + vec.reserve(5); + + vec.emplace_back("First"); + vec.emplace_back("Second"); + vec.emplace_back("Third"); + vec.emplace_back("Fourth"); + vec.emplace_back("Fifth"); + + EXPECT_EQ(Tracer::str_ctor, 5); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::alive, 5); +} + +Tracer CreateTracer() { + return Tracer("Hello"); // RVO +} + +TEST_F(TracerTest, ReturnValueOptimization) { + Tracer obj = CreateTracer(); + + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); +} + +TEST_F(TracerTest, MultipleObjectsConsistency) { + { + Tracer a("A"); + Tracer b = a; // copy ctor + Tracer c = std::move(a); // move ctor + + std::vector vec; + vec.reserve(2); + vec.push_back(b); + vec.push_back(std::move(c)); + + a = b; // copy assignment + b = std::move(vec[0]); // move assignment + } + + int total_created = Tracer::default_ctor + Tracer::str_ctor + Tracer::copy_ctor + + Tracer::move_ctor; + + EXPECT_EQ(total_created, Tracer::dtor); + EXPECT_EQ(Tracer::alive, 0); + EXPECT_EQ(Tracer::count, total_created); +} + +TEST_F(TracerTest, DataMethodReturnsValidPointer) { + Tracer obj("Test"); + const char* ptr = obj.Data(); + + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(ptr[0], 'T'); + EXPECT_EQ(ptr[1], 'e'); + EXPECT_EQ(ptr[2], 's'); + EXPECT_EQ(ptr[3], 't'); + + Tracer copy = obj; + EXPECT_NE(obj.Data(), copy.Data()); + EXPECT_STREQ(obj.Data(), copy.Data()); +} + +TEST_F(TracerTest, ResetStatsWorks) { + Tracer a("A"); + Tracer b = a; + + EXPECT_GT(Tracer::count, 0); + EXPECT_GT(Tracer::alive, 0); + + Tracer::ResetStats(); + + EXPECT_EQ(Tracer::count, 0); + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::str_ctor, 0); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::move_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); + EXPECT_EQ(Tracer::alive, 0); + + EXPECT_FALSE(a.Name().empty()); + EXPECT_FALSE(b.Name().empty()); +} + +TEST_F(TracerTest, ChainedAssignments) { + Tracer a("A"); + Tracer b("B"); + Tracer c("C"); + + a = b = c; + + EXPECT_NE(a.Name().find("C_"), std::string::npos); + EXPECT_NE(b.Name().find("C_"), std::string::npos); + EXPECT_NE(c.Name().find("C_"), std::string::npos); + + EXPECT_EQ(a.Id(), 1); + EXPECT_EQ(b.Id(), 2); + EXPECT_EQ(c.Id(), 3); + + EXPECT_EQ(Tracer::copy_assign, 2); +} + +TEST_F(TracerTest, TemporaryObjectInExpression) { + Tracer a("Start"); + a = Tracer("Temp"); + + EXPECT_EQ(Tracer::str_ctor, 2); + EXPECT_EQ(Tracer::move_assign, 1); +} + +TEST_F(TracerTest, StdSwapUsesMoveOperations) { + Tracer a("ThisNameRequireLongStringToAvoidSSO"); + Tracer b("LongStringToAvoidSSO"); + std::string name_a = a.Name(); + std::string name_b = b.Name(); + + const char* a_ptr_before = a.Data(); + const char* b_ptr_before = b.Data(); + int a_id = a.Id(); + int b_id = b.Id(); + + std::swap(a, b); + + EXPECT_EQ(a.Name(), name_b); + EXPECT_EQ(b.Name(), name_a); + + EXPECT_EQ(a.Id(), a_id); + EXPECT_EQ(b.Id(), b_id); + + EXPECT_EQ(a.Data(), b_ptr_before); + EXPECT_EQ(b.Data(), a_ptr_before); + + // std::swap uses move construction and move assignment + EXPECT_EQ(Tracer::move_ctor + Tracer::move_assign, 3); +} diff --git a/05_week/tasks/tracer/tracer.cpp b/05_week/tasks/tracer/tracer.cpp new file mode 100644 index 00000000..2ccfb417 --- /dev/null +++ b/05_week/tasks/tracer/tracer.cpp @@ -0,0 +1,6 @@ +#include + + +class Tracer { + +}; \ No newline at end of file diff --git a/cmake/TestSolution.cmake b/cmake/TestSolution.cmake index acc27ee1..692da330 100644 --- a/cmake/TestSolution.cmake +++ b/cmake/TestSolution.cmake @@ -11,6 +11,16 @@ function(add_gtest TARGET) ) endfunction() +function(add_gtest_asan TARGET) + add_psds_executable(${TARGET} ${ARGN}) + target_compile_options(${TARGET} PRIVATE -g -fsanitize=address,undefined) + target_link_libraries(${TARGET} PRIVATE GTest::gtest GTest::gtest_main) + target_link_options(${TARGET} PRIVATE -fsanitize=address,undefined) + set_target_properties(${TARGET} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_TASKS_DIR} + ) +endfunction() + function(add_example NAME) if(BUILD_EXAMPLES) add_psds_executable(${NAME} ${ARGN}) diff --git a/grading/deadlines.json b/grading/deadlines.json index d9138f50..630f54c1 100644 --- a/grading/deadlines.json +++ b/grading/deadlines.json @@ -131,5 +131,26 @@ "max_score": 400, "deadline": "2025-12-26 23:59", "description": "Комплексная амплитуда" + }, + + "tracer": { + "max_score": 100, + "deadline": "2026-02-16 23:59", + "description": "Трассировщик" + }, + "string_view": { + "max_score": 200, + "deadline": "2026-02-16 23:59", + "description": "Представление строки" + }, + "cow_string": { + "max_score": 300, + "deadline": "2026-02-16 23:59", + "description": "Коровья строка" + }, + "simple_vector": { + "max_score": 400, + "deadline": "2026-02-16 23:59", + "description": "Простой вектор" } } \ No newline at end of file