Задать вопрос
Ответы пользователя по тегу C++
  • Что происходит на уровне памяти при обращение к полям класса?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Компилятор знает, по какому смещению относительно начала класса лежит каждое поле. Поэтому при обращении к isVisible будет одно чтение по адресу именно где оно лежит. Доступа к предыдущим полям не будет. Если они попали в одну кеш-линию, то они все загрузятся в кеш. Но при этом они могут быть в разных кеш линиях, даже если в классе идут совсем подряд, зависит от конкретного расположения в памяти всех полей, если граница проходит как раз между ними. Это может быть если класс больше 64 байт и первые 64 попали в одну линию, а следующий член класса уже в соседнюю. Или если класс меньше, но у него выравнивание, скажем. 4 байта. и он может лежать за 4 байта до конца линии и там уже второй int в кэш линию не влезет вместе с первым.

    Создавать локальную переменную не надо. Чаще всего компилятор сам поймет, что тут обращение к одному и тому же адресу и туда никто ничего не писал и можно просто загрузить один раз в регистр и все.
    Только если в в профилировщике видите что вот тут у вас узкое место и в ассемблере видите, что да, там есть несколько обращений, тогда имеет смысл вводить переменную. Но это весьма редкий случай. Перегружать код ради этой преждевременной оптимизации не стоит.
    Ответ написан
    Комментировать
  • Reinterpret_cast вектора типа double в T неопределенное или определенное поведение?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Это Undefined behavior, конечно. Пока луна не в скорпионе, оно даже может работать, потому что расположение в памяти Time и double одинаковое, и вроде как даже vector для этих двух типов будут в памяти одинаковы.

    Тут проблема даже не reinterpret_cast, кастовать-то можно: пункт 6, а потом пункт 5.

    Обращаться к этому потом нельзя.
    Тут нарушение strict aliasing. Нельзя к данным vector<Time> обращаться через указатель на vector<double>. Компилятор в праве посчитать, что вы к вектору v вообще не обращаетесь нигде и оптимизировать его нафиг из программы и памяти.

    Раз расположение в памяти идентичное, вы можете выделить вектор Time нужного размера и скопировать туда данные через memcpy:
    memcpy(&t[0], &v[0], sizeof(double)*v.size());

    Или напишите свою функцию в виде шаблона.
    template <typename T> 
    std::vector<T>Interpolate(vector<T> data) {
      // ...
    }


    Тогда она может и double и Time принимать без проблем.

    Или напишите конструкторы для Time, И можно его инициализировать через список инициализации:
    struct Time {
            Time() : x(0) {}
            Time(double a) : x(a) {}
            double x;
        };
    
    std::vector<Time> v = {1,2,3};


    С такими конструкторами можно даже скопировать vector<double> в vector<Time>:
    std::copy(a.begin(), a.end(), std::back_inserter(v));


    Edit: ну, и наоборот, если вы хотите из вектора Time получить вектор double, вы должны определить operator double() у вашего Time. И оттуда уже можно будет копировать в дабловый вектор. И работать это будет даже если класс Time содержит еще чего-то кроме double.
    Ответ написан
    3 комментария
  • Заголовочные файлы в Си нужны только для интерфейса?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Для того, чтобы использовать в main.c функцию, определенную в math_functions.c, вам надо иметь ее объявление в main.c

    Вам надо сказать компилятору, что вот такая функция есть и у нее вот такие вот параметры.

    Можно просто в main.c написать то, что вы бы написали в math_functions.h. Но это быстро становится сложно, если у вас проект большой и функции используются в разных файлах. Надо будет эти объявления копировать в кучу мест. А если вам еще и поменять что-то надо потом, вы офигеете. Для этого и придумали заголовочные файлы - вы пишите объявление один раз и потом его везде используете.

    Вообще, сейчас не обязательно math_functions.h включать и в math_functions.c. Раньше надо было определять функцию даже если ее объявления нигде нет. В современных стандартах это не так. Но все равно хедеры включают в соответствующий файл, чтобы ловить ошибки. Если вы поменяете функцию в .c но не поменяете в хедере, компилятор заметит несоответствие объявления и определения и сообщит об ошибке.
    Ответ написан
    6 комментариев
  • Как решить ошибку "Код инcтрументирования объектов cookie стека обнаружил переполнение буфера, связанное со стеком"?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Давайте код функции текстом вставляйте. Ошибка говорит о том, что вы где-то вылезли за границы массива на стеке (локального), и перезаписали то, что не нельзя было перезаписывать. Ошибка в логике программы и работе с памятью.
    Ответ написан
    Комментировать
  • Файл cpp не видит библиотеку из другого файла как решить проблему?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Вы про ошибку "Use of undeclared identifier 'cin'"?

    Это потому что cin и cout находятся в пространстве имен std. Надо или писать std::cin, или использовать using namespace std;. Но вот эта вторая конструкция у вас написана в файле main.cpp, только в нем и действует. Она не распространяется на исходник функции fu1, которая находится вообще в другом файле.

    Компилятор отдельно компилирует каждый cpp файл, генерирует машинный код с пометками, где какая функция лежит. Что вы там в main.cpp понаписали в io.c не имеет никакого значения.
    Ответ написан
    2 комментария
  • Как правильно реализовать блок памяти для сильной и слабой ссылки?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Тут есть два варианта: 1) вы вызываете деструктор, но не освобождаете память, когда все сильные ссылки кончились. В этом случае надо воспользоваться type erasure, чтобы выключить деструктор. Деструктор же вы вызываете вручную, когда надо, через reinterpret_cast(storage_)->~T(); Посмотрите вот тут пример: https://chromium.googlesource.com/chromium/src/bas...
    2) Вы вызываете деструктор и освобождаете память. В этом случае Value обязано быть в другом блоке памяти, а в вашем умном указателе будет лишь указатель на него. Ибо нельзя никак вернуть часть блока.
    Ответ написан
    4 комментария
  • Почему не работает передача контекста между приложениями?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Указатель - это адрес, который работает только в адресном пространстве одного приложения. Все приложения независимы, у них у каждого свое адресное пространство и один и тот же адрес может хранить совершенно разные вещи в разных приложениях (есть исключения, но не будем в эти детали залезать).
    Нельзя просто передать указатель (адрес) в другое приложение и там с ним работать.

    В документации указано, что можно использовать OpenGL контекст другого окна, но возможно это относится только к окнам внутри одного приложения и тогда вашу задумку вообще нельзя осуществить как вы хотите.

    Чтобы что-то делать с ресурсами другого приложения вам надо эти ресурсы передать средствами операционной системы, через хэндлы (на винде).
    Попробуйте получить хендл окно с контекстом, потом как-то из него создать GLFWwindow (тут я не нашел, возможно ли это), и его уже использовать в качестве параметра для создания нового контекста, который окажется расшарен с предыдущим. Хендл искомого окна получайте через EnumWindows.

    Но скорее всего ваша задумка невозможна.
    Ответ написан
    3 комментария
  • Как узнать, хранятся числа в компьютере в прямом, дополнительном или обратном коде?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Можно переинтерпретировать память как unsigned int, а там уже никакого дополнительного кода нет и 11111111111111111111111111111110 будет 4294967294, а 11111111111111111111111111111101 - 4294967293.
    #include <iostream>
    #include <cstring>
    int main() {
      int x = -2;
      unsigned int y = 0;
      memcpy(&y, &x, sizeof(y));
      std::cout << y;
      return 0;
    }

    Еще можно делать извращение вроде std::cout << *(unsigned int*)(&x), но не стоит.
    В случае обращения к int через указатель на unsigned int это еще нормально, но вообще говоря, это исключение. Обычно нельзя обращаться к переменной одного типа через указатель другого типа. Это неопределенное поведение. Таким вот образом посмотреть на расположение битов в float через преобразование к int нельзя.

    Копирование же в переменную другого типа но такого же размера через memcpy - сработает всегда. При этом компилятор копирование убирает при оптимизации, так что правильное решение работат также быстро, как с Undefined Behavior.

    Примерно также можно определить и порядок байт в числе, надо будет копировать в массив из байт и выводить уже побайтово.
    Ответ написан
    4 комментария
  • Имя массива это адрес первого элемента или указатель на его первый элемент в Си?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    arrOne и arrTwo - это переменные, типов int[5] и int[3][2]. Это на уровне C. Под капотом там указатели: компилятор знает адрес начала массива. Путаницу может вызвать то, что массивы в языке C тождественны указателям на начало с той лишь разницей, что компилятор знает его длину и всякие sizeof() сработают правильно.
    Соответственно, arrOne неявно приводится к int*, а arrTwo к int (*)[2] (указатель на массив из 2 элементов, что в свою очередь неявно приводиться к указателю на указатель на инт).

    Поэтому работает код:
    int a[3][2];
       int (*b)[2];
       b = a;


    Можно считать, что arrOne - указатель на первый элемент int, а arrTwo - указатель на первую строку int[2], по совместительству на первый элемент в первой строке:
    int x = *arrOne;
       int y = **arrTwo; // с одной * не сработает ибо int[2], оно же int* к int не приводиться.
       int *z = arrOne+2; // указатель на 3-ий элемент
       int *w = arrTwo + 1; // указатель на вторую строку (первый элемент в ней).


    Но лучше на закапываться в адрессную арифметику, а воспринимать массивы, как свой отдельный тип данных. Не пишите arrOne+1, а пишите arrOne[1] и не парьтесь.

    Даже для int переменных компилятор знает их адрес, так что там под капотом тоже есть указатель. Не очень большая разница.

    Массив - это индексируемая область памяти. Единственное, о чем надо помнить, что массивы всегда передаются по ссылке. Их дешево передавать в функции и изменения массива внутри всегда видны снаружи.
    Ответ написан
  • Влияет ли, передаёшь ты в функцию аргументы по ссылке или по значению, на производительность и память?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    По ссылке дешевле во многих случаях. Ибо передача по значению создает копию. Поэтому передача большого вектора по значению будет очень дорогой.

    В целом, в С++ хорошее правило - передавать большие переменные как const &.

    Но для мелких типов, вроде int, передавать по ссылке может быть даже дороже, ибо и ссылка и значение тупо помещаются в регистр. Но ссылку надо будет еще разыменовывать, чтобы прочитать значение из памяти, а значение из регистра компилятор может даже не сохранять в память.

    С другой стороны, компилятор может наоптимизировать и даже ссылка на int будет не дороже копии int.

    Сразу говорить про все языки программиования нельзя. В некоторых языках вообще все передается по ссылке, в других вообще нет разницы для мелких типов.
    Ответ написан
    Комментировать
  • Как понять что переполняет память в C++?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    С чего вы взяли, что переполняется память? Если диспетчер задач пишет о всего-то 20мб?

    Потребляет ли много памяти какой-то другой процесс?

    Возможно там утечка каких-то ресурсов, вроде хендлов.

    Каким-нибудь process explorer можно посмотреть сколько хендлов ваша программа имеет, если это число растет, надо разбираться, какие из виндовых объектов вы правильно не уничтожаете. Вроде бы все битмапы вы корректно уничтожаете через DeleteObject, возможно ошибка в другом коде.

    Можно еще поробовать вашу программу подебажить. Вот когда окно вылезает, какой ваш код выполняется? Подключитесь к вашему процессу visual studio и смотрите. Или добавьте отладочный вывод, чтобы понять, какая функция начинает выполнятся но не заканчивает. Это подскажет вам, какие объекты вы не удалили правильно.
    Ответ написан
  • Какой подход выбрать для представления Chunk?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Зависит от того, как вы планируете эти chunk использовать. subrange - гораздо эффективнее, потому что ничего не копируется. Но при этом он не может использоваться после уничтожения оригинального items, или chunks (если внутри у chunk все-таки копия).

    В приведенном вами коде вы с чанками работаете локально, ни в какие хранилища или очереди для обработки в других функциях или потоках не складываете, поэтому вам подойдет subrange.
    Ответ написан
  • Как определить виртуальные методы в полной специализации шаблона?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Можно каждый метод отдельно в шаблон заворачивать, тогда не надо будет их все в каждой специализации копировать.
    Ответ написан
    Комментировать
  • Почему я могу изменять состояние объекта хранящийся в const std::unique_ptr и const std::shared_ptr?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Можно сделать указатель на const. Вот этот ваш const, он относится к самому указателю, его нельзя менять (в смысле, на другой адрес). Но после разыменовывания получается не константная ссылка. Вот оно в документации:
    typename std::add_lvalue_reference<T>::type operator*() const


    Там нет никакого const в типе возвращаемого значения, несмотря на то, что оператор можно вызывать у константных экземпляров класса. Зачем конкретно так сделано, я не знаю. Наверно, тут копируется поведение обычных указателей: там тоже можно иметь неизменный указатель на изменяемую область памяти.

    Так что если вы хотите запретить менять объект, то можно сделать так:
    void foo(const std::unique_ptr<const int>& ptr) {
        if (ptr) {
            *ptr += 5; // Ошибка компиляции.
            std::cout << *ptr;
        }
    }
    
    int main() {
        std::unique_ptr<const int> ptr = std::make_unique<const int>(5);
        foo(ptr);
    }


    Правда, придется писать много лишнего кода, если будете передавать неконстантный объект внутрь функции, которая хочет ссылку на константный.

    Но вообще, обычно нет смысла передавать unique_ptr по ссылке. Можно передать по ссылке сам объект, все равно владение в функцию не передается же. И уже там можно навешивать const, если надо. Или, если передавать просто unique_ptr, без ссылки, то даже лишнего кода не надо для обработки const:
    void foo(const std::unique_ptr<const int> ptr) {
    }
    
    int main() {
        std::unique_ptr<int> ptr = std::make_unique<int>(5);
        foo(std::move(ptr));
    }
    Ответ написан
    3 комментария
  • Почему возникает ошибка C2512 в конструкторе с std::initializer_list?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    У вас конструктор не передает initializer_list в поле arr[N], а сначала конструирует массив по умолчанию, а потом туда копирует ручками значения из initializer_list. Перед выполнением тела конструктора вся память должна быть выделена и все не иницилизированные явно поля инициализируются по умолчанию.

    В качестве решения можно весь ваш ручной код выкинуть и передать список в конструктор arr:
    static_array(std::initializer_list<type> il)
    		requires std::is_copy_constructible_v<type> : 
            arr(std::forward(il)) {}


    Если хотите проверить длину списка, можно это все еще сделать потом в теле конструктора.
    Аналогично в остальных случаях.

    Но вообще, можно не расписывать разные конструкторы, а сделать только один static_array(T&& init), где этот init и передавать в конструктор arr.

    Если вы так хотите руками данные копировать через ваш place_at, то вам надо ваш array сделать типа char или uint8_t, выровнять и задать нужный размер через sizeof(type). Простой случай показан тут.

    А вообще, чем вам std::array не нравится?
    Ответ написан
  • Почему окружность получается отрисованной не ровно?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Это называется aliasing. Ну не ложится гладкая окружность (да и наклонные прямые) на прямоугольную сетку пикселей. SDL renderer похоже слишком примитивен и умеет красить только черные и белые пиксели. Если бы он где-то рисовал серые, картинка выглядила бы более гладкой.

    Если вас это качество не устраивает, то вам надо найти какой-то алгоритм рисования окружности по пикселям и руками его реализовать (что-то вроде этого). Или реализовывать (сглаживание) anti-aliasing. Например, можно нарисовать сцену в текстуре в 2-3-4 раза больше нужного размера, потом ее отмасштабировать до размера на экране и уже это показывать.

    Еще где-то пишут, что если использовать GPU а не software рендеринг в SDL то качество получше.
    Ответ написан
    3 комментария
  • Почему не получается вывести тип шаблона?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Проблема в том, что TInput с одной стороны выводится из первого аргумента, там у вас BuildableProperty<...,...,const Cursor&> согласно определению CursorProperty.

    Но третий аргумент выводит TInput как просто Cursor, потому что это аргумент функции, а там передача по ссылке, даже в шаблонах, должна быть указана явно. Cursor != Cursor&, это разные типы.

    Один из способов это починить - в объявлении SetProperty:
    SetProperty(..., typename std::remove_reference<TInput>::type value)


    Теперь, даже если в первом аргументе в BuildProperty используются ссылки, они не будут мешать в третьем аргументе.

    Но вообще, по уму вам бы ваш шаблон BuildableProperty пофиксить. Вот у вас для каких-то случаев надо в нем ссылки использовать. Так может ссылки имеет смысл использовать всегда? Тогда вы в шаблоне BuildableProperty предполагайте что T, TInput - это просто типы, без ссыльности и константности, и внутри где надо расставьте &.

    Не надо для каких-то случаев "передавать" в шаблон ссылки на какие-то типы.
    Ответ написан
    1 комментарий
  • Почему умные указатели нельзя интегрировать в язык?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Можно, но нужно ли? Польза только в том, что вы экономите 6 симолов (std:: уже можно убрать через using namespace std например). И то, только в случае, когда вам нужен именно вот такой вот указатель. А если вам нужен unique_ptr, а если вам нужен ваш собственный умный указатель, который как-то по другому память выделяет? А если вам надо не make_shared вызывать, а какой-то фабричный метод? Плюс это добавит кучу вопросов вроде, а как будет указатель на указатель? Указатель обычно идет после типа вроде int*, почему shared вы ставите перед ним?

    В общем, возни много, а польза весьма ограниченная.
    Ответ написан
    2 комментария
  • Как ждать в диапазоне атомарных чисел?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    То, что вы описали в комментариях, это буквально блокировка чтения-записи. Функтор читает данные и может выполнятся параллельно несколько раз, но запись может быть только одна и она блокирует все чтения. Решение описано в википедии: 2 мьютекса, один счетчик. Атомиков не надо.
    Ответ написан
    Комментировать
  • Почему возникает ошибка при вызове вирутального метода в "operator="?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Во-первых, оператор не виртуальный.

    Во-вторых, это проблема из-за правил поиска операторов. Они ищутся только в типах, которые участвуют в выражении, т.е. int и WidthProperty.

    Если хотите использовать оператор из родительского класса, надо в дочернем сделать
    using Base::operator=;

    Что бы протащить этот метод в пространство имен дочернего класса, где компилятор его найдет.
    Ответ написан
    1 комментарий