Information
- Rating
- Does not participate
- Registered
- Activity
Specialization
Инженер встраиваемых систем, Архитектор программного обеспечения
Ведущий
C++
C++ builder
C
ООП
Разработка программного обеспечения
Visual Studio
C++ stl
Оптимизация кода
Многопоточность
Объектно-ориентированное проектирование
Сам давно знаю про SQLite, о том что это лёгкая база данных и так далее, но самостоятельно с ней не работал. На данный момент времени решил всё таки рискнуть попробовать воспользоваться UI (SQLiteStudio) и в принципе всё хорошо работает, быстро и интуитивно разобрался, хоть и есть визуальные баги.
Но вопрос стоит такой: Могу ли я скопировать структура базы данных Oracle c одной БД в мою SQLiteStudio? Если да, то как и есть ли инструкция/документация? Очень хотелось бы интегрировать её.
Заранее благодарю за ответ и в принципе за разработку данного приложения!
И да, по поводу визуальных багов, это не видно как свернуть окно редактора и несмотря на выбор русскоязычного перевода, часть интерфейса всё равно на английском, что выглядит немного кринжово, если честно.
Отладка, конечно, важна, особенно для такого системного языка, как C++. Однако механизмы вроде полной рефлексии или детального доступа к приватным данным могут быть реализованы не стандартом, а средствами компилятора — например, в debug-режиме. Это позволяет сохранить философию языка, связанную с защитой инкапсуляции, и при этом давать разработчикам нужный функционал.
Более того, некоторые "расширения", вроде RTTI, уже показывают, что такое разделение возможно: стандарт определяет интерфейс, а реализация остаётся на усмотрение компилятора. В будущих версиях языка ожидается полноценная поддержка рефлексии, которая должна быть добавлена именно таким образом — без нарушения ключевых принципов C++.
Моя цель — сохранить концепцию POD-массива: не вызывать конструкторы и деструкторы, хранить данные в нулевой памяти и использовать пользовательский аллокатор. При этом можно сделать класс более совместимым со стандартными механизмами. К примеру, для этого можно перейти с прямых вызовов
allocateиdeallocateна использованиеstd::allocator_traits<Allocator>, чтобы обеспечить поддержку разных типов аллокаторов, включая stateful и полиморфные. Также можно добавить корректную обработку политик распространения аллокаторов, таких какpropagate_on_container_copy_assignment,propagate_on_container_move_assignmentиpropagate_on_container_swap, чтобы правильно управлять тем, как аллокатор передаётся при копировании, перемещении и свопе. Кроме того, можно реализовать копирующий и перемещающий конструкторы, а также операторы присваивания так, чтобы они учитывали аллокатор и соблюдали ожидаемое поведение стандартных контейнеров. Наконец, можно обеспечить совместимость с механизмамиstd::uses_allocatorиstd::scoped_allocator_adaptor, чтобы контейнер можно было безопасно использовать внутри других стандартных абстракций. Это позволит сохранить изначальную идею POD-хранилища.Если вам действительно необходим этот функционал, вы можете сделать форк проекта и реализовать поддержку политик распространения аллокаторов, корректную работу с
std::allocator_traits, а также добавить совместимость сstd::uses_allocatorиstd::scoped_allocator_adaptorВы абсолютно правы — в статье действительно не хватает важных предупреждений о рисках использования описанного подхода, а также ссылок на современные и безопасные альтернативы. Это упущение с моей стороны.
Что до вопросов ответственности — в следующих материалах я обязательно это учту.
Что касается дальнейшей дискуссии — считаю, что мы сейчас выходим за рамки программирования и технической части статьи, поэтому давайте оставим разговор здесь. Ещё раз благодарю за замечание — оно поможет сделать материал лучше.
Вы правы: описанный подход формально является неопределённым поведением согласно стандарту C++. Мы не создаём объект в строгом смысле — мы просто интерпретируем сырую память как объект. Это UB, и это нельзя игнорировать.
Однако цель статьи — не дать рекомендации к использованию в production-коде, а показать, как устроена работа с памятью «под капотом» , и какие техники применяются в определённых нишевых задачах: сериализация, memory-mapped I/O, реализация собственных контейнеров, low-level оптимизации.
Фраза про "работает в C++17" — это констатация факта, а не призыв использовать такой код везде. Да, современные компиляторы могут начать активно эксплуатировать такое UB, особенно с появлением
std::start_lifetime_as. Это действительно может привести к внезапным багам после обновления компилятора — вы абсолютно правы.Но если обновился компилятор, и код перестал работать — это проблема разработчика , а не языка. Если он выбрал путь работы с raw-памятью, значит, он должен понимать, что делает, и быть готов к последствиям. В C++ вам дают пистолет. А уже вам решать — стрелять ли из него, и куда. Компилятор же лишь периодически меняет направление ствола, пока вы не глядя жмёте на спусковой крючок.
Так что пусть статья будет не руководством к действию, а скорее картой минного поля — чтобы те, кто всё равно решится пройти этим путём, хотя бы знали, где они идут.
И не стоит меня обвинять за то, что кто-то применит этот материал не по назначению. Как не обвиняют создателя пистолета в том, что вы себе отстрелили ногу. Это ваш выбор. И ваша ответственность
Цель статьи — не навязывать стиль программирования или подходы, а показать, как работают механизмы языка в нестандартных ситуациях. Это скорее технический эксперимент и разбор того, что возможно в C++, когда хочется сделать то, что "по документации нельзя". Ваш комментарий абсолютно верный — в массовом применении такие трюки могут быть избыточными или даже вредными. Но в узких, специфических задачах они могут дать полезное понимание устройства языка и памяти.
Согласен, если просто запрещать конструкторы, а потом их героически обходить — это похоже на игру в кошки-мышки.
Но цель статьи — показать не «как надо», а «что возможно». Это скорее исследование, чем рекомендация.
То есть, это не про лучшие практики для повседневного кода, а про то, как устроен язык изнутри, и какие возможности он даёт тем, кто хочет заглянуть за границы привычного.
Вы правы, этот код потенциально опирается на поведение, не гарантированное стандартом. Однако важно учитывать контекст: статья рассматривает практики, применимые в C++17 , где ещё не было
std::start_lifetime_as, и где компиляторы действительно трактовали такие конструкции как рабочие.Да, с точки зрения современных стандартов (например, C++20 и новее), многие практики из C++17 могут считаться потенциально некорректными. Но если речь о реальности C++17 — такие подходы остаются рабочими
Иногда намеренно удаляют конструктор по умолчанию, даже если он тривиальный — например, у простых структур (POD), которые содержат только данные. Это делается не потому, что конструктор вреден, а чтобы запретить создавать объекты случайно или без явной инициализации .
Допустим есть структура
Point { int x; int y; }. Можно создать её так:Point p;Но тогда поля
xиyбудут иметь неопределённые значения. Это может привести к багам, если этот объект представляет с собой объект в пространстве для какой-то системы. Для это и удаляют конструктор по умолчаниюПроблема не только в неопределённом поведении (UB) , но и в том, что для доступа к одному приватному полю или методу приходится создавать полностью совпадающую "зеркальную" структуру , повторяющую весь layout исходного класса.
Например, если тебе нужно получить доступ всего к одному полю, находящемуся на 100-й позиции в списке полей класса, всё равно приходиться вручную перечислить предыдущие 99 полей в правильном порядке и с тем же типом. Это не просто неэффективно — это практически нереализуемо в реальных проектах.
Тоже вариант. Вот пример доработки вашего метода
Проблема данного подхода в том, что он опирается на конкретное расположение полей в классе
Fruit. Если изменить порядок следования полей в классе или добавить/удалить какие-либо из них, структураFruitAccessorперестанет соответствовать layout-у классаFruit, и доступ к памяти черезreinterpret_castстанет некорректным. Это приведёт к неопределённому поведению (undefined behaviour)friend— вполне нормальный и рабочий способ дать доступ к приватным членам другим функциям или классам. Он есть в C++ с самого начала и работает хорошо. Данный "велосипед" необходим, если нельзя изменить исходный код класса — например, он из библиотеки. Или если вам нужно получить доступ ко всем приватным полям и методам, а не только к одному конкретному. Ещё бывает удобно сделать это автоматически, через шаблоны, без ручного указания каждого члена. Вот тогда и придумывают разные хитрые способы, подобно этому но при этом сохранить типобезопасность и работать на этапе компиляции.Использование
#define private publicможет вызвать побочные эффекты, поскольку препроцессор меняет не только нужное вам слово в конкретном классе, но и во всех заголовочных файлах, которые вы подключаете после этой директивы — в том числе из стандартной библиотеки или сторонних библиотек.Хотя этот способ и позволяет обойти ограничения доступа быстро, он не является решением задачи в правильном смысле. Это скорее временная заплатка, которую лучше использовать только в крайних случаях, если других вариантов действительно нет. Вместо этого стоит применять более безопасные и изящные методы, например, шаблочные подходы, описанные в статье, которые позволяют получать доступ к приватным членам легально и с соблюдением типобезопасности.
Потому что C++ — это язык, в котором инкапсуляция — не просто ограничение доступа, а один из фундаментальных принципов проектирования. Стандарт не предусматривает механизмов для нарушения приватности даже с учётом нужд отладки, ведь добавление такого функционала противоречило бы самой философии языка. Программа должна быть строгой и защищённой по своей природе, а если разработчику нужно «заглянуть внутрь» объекта — это уже его личная ответственность и дело техники, а не задача языка.
Метод использует легальные средства языка C++ , включая шаблоны, указатели на члены класса, статическую инициализацию и явную специализацию. Все эти механизмы описаны в стандарте C++ и не предполагают undefined behavior при корректном использовании.
Доступ к приватным членам возможен только в контексте, где они видны компилятору , то есть внутри области, доступной на этапе компиляции. Попытка применить этот механизм к чужому классу без полного определения приведёт к ошибке компиляции — это гарантирует отсутствие произвольного доступа.
Все операции происходят на этапе компиляции , что исключает runtime-обход защиты или манипуляции памятью. Это позволяет компилятору полностью контролировать типобезопасность и layout объектов.
Отладка — это внешний процесс, и она не должна влиять на дизайн API. Язык не предоставляет стандартных способов доступа к приватным членам, потому что инкапсуляция — ключевой принцип ООП. Любые обходные пути возникают не из-за недостатков С++, а как побочный эффект его гибкости и низкоуровневой природы. Сказал бы, что не баг, а особенность языка: даёт контроль, но не навязывает ограничения через силу.
Cледует различать два принципиальных аспекта: неопределённое поведение (undefined behaviour, UB) и доступ к данным.
С точки зрения UB, использование указателей на члены класса для получения доступа к приватным полям не приводит к неопределённому поведению при соблюдении всех правил работы с такими указателями. Если компилятор знает типы и они корректно используются, то такой способ остаётся в рамках стандарта C++. То есть формально никакого нарушения стабильности или предсказуемости программы не происходит — всё выполняется строго по правилам языка.
Можно провести параллель с отладчиком. Допустим, мы создаём объект класса
Dog, передавая ему имя через конструктор, но ни одно поле или метод не являются публичными. Но любой отладчик покажет содержимое внутренних полей, потому что имеет информацию о структуре класса и может прочитать память. Описываемый подход делает то же самое, только программно. Он позволяет получить доступ к внутреннему состоянию объекта даже тогда, когда публичный интерфейс не предоставляет такой возможности. Это не обход защиты, а скорее расширение возможностей разработчика в сложных ситуациях.Даже если использовать этот способ для работы с классами из сторонней библиотеки, при этом вы работаете с объектами, созданными вами лично. Вы контролируете их жизненный цикл, инициализацию и данные. То есть взаимодействуете со своей собственной памятью, а не получаете доступ к защищённой или чужой области памяти. В этом смысле данный подход ничем не отличается от того, что делает отладчик — он просто использует известную структуру класса для просмотра или изменения его состояния.
Этот метод не создаёт реальных дыр в безопасности, которые можно было бы использовать злоумышленнику. Он работает исключительно внутри вашей программы, при наличии полного контроля над кодом и сборкой. Это не обход ограничений, а возможность, аналогичная возможностям отладчика. При грамотном использовании она не представляет опасности. Если же такой приём используется неправильно — это уже вопрос квалификации и ответственности разработчика, а не самого механизма.
Если вы ожидаете доказательства уровня стандарта — оно содержится в том, как работают указатели на члены, шаблоны и статическая инициализация. Если же речь о практической безопасности — примеры из статьи демонстрируют стабильное и предсказуемое поведение метода.
Да, это действительно запасной ключ потому что метод опирается на существующие языковые конструкции — шаблоны и статические инициализаторы — которые работают на этапе компиляции, не нарушая абстракции памяти или ABI, и не требуют изменения исходного кода класса.
Спасибо за интерес к статье
Это не объяснение, а тавтология. Да, программировании ограничения существуют не просто так: они служат для поддержания инвариантов, предотвращения несанкционированного доступа, обеспечения безопасности и читаемости кода. Однако бывают ситуации, когда эти ограничения необходимо преодолеть — например, при тестировании legacy-кода без возможности изменения интерфейса, работе с закрытыми API (например, встраивание функционала в чужой движок), исследовательских целях или reverse-engineering'е
То, что технически запрещено, не всегда является запретом на использование в специфических условиях. Это как сказать, что у автомобиля есть замок зажигания, значит его вообще нельзя завести без ключа. Но в сервисном центре или при диагностике иногда это нужно — и для этого тоже есть способы, только делают это опытные люди с пониманием последствий.
Это неверно с точки зрения реализации и стандартов языка. Компилятор запрещает из-за правила [class.access] стандарта C++, которое регламентирует видимость членов класса. Это не произвол — это формальная часть языка, обеспечивающая защиту от случайного или намеренного нарушения инкапсуляции. В реальности вы просто повторяете то, что уже указано в статье.
Метод, описанный в статье, использует шаблоны и статические инициализаторы, которые работают на этапе компиляции и не нарушают абстракцию памяти или ABI. Он не использует
const_cast,reinterpret_castили другие потенциально опасные конструкции. Он не обходит защиту памяти или runtime-проверки — он просто использует легальные механизмы языка, позволяющие получить доступ к приватным членам через явную специализацию шаблонов, которая происходит в допустимом контексте.Это не "дырка в заборе", это скорее "ключ к резервному входу". Использование такого способа требует знания того, что вы делаете.
Да, можно. Но тогда вы усложните язык, сделаете невозможными многие легитимные вещи вроде SFINAE, частичной специализации, трейтов, сериализации и других продвинутых возможностей. Язык C++ построен на принципе: вы имеете право сломать себе ногу, если вы сами взяли молоток и гвозди.
Такие техники, как описанная в статье, не являются злоупотреблением — они расширяют границы возможного в условиях ограниченного доступа к исходникам, устаревшего кода или необходимости глубокого анализа объектов.
Да, вы правы: я не тестировал чужой legacy-код, который сломается, если я не достану изнутри private-поле. Я не сидел в уголке, зажав в руках документацию от библиотеки 1993 года, и меня никто не заставлял это делать.
Но знаете что? Это не делает мой подход бесполезным.
Если бы все писали статьи только про то, что сильно болит , мы бы до сих пор писали на ассемблере — потому что "ну вот так нам нужно". Иногда исследование возможностей ради интереса приводит к чему-то полезному. Иногда оно рождает инструмент, который через пару лет спасёт чью-то пятую точку. А иногда просто демонстрирует, что язык ещё не догнал своими ограничениями возможности разработчиков.
Моя задача была не в том, чтобы решить конкретную проблему здесь и сейчас, а показать технически корректный способ , как можно обойти ограничения доступа без нарушения семантики языка. Ни один reinterpret_cast не пострадал. Не было #define-хаков, макросных трюков или указателей void*. Всё работает строго на этапе компиляции, используя механизмы, которые и так есть в C++: шаблоны, специализации, статическую инициализацию.
Это паттерн использования языка , который позволяет вам получить доступ к данным, не нарушая при этом ABI, memory model и логику типобезопасности. Такие вещи важны, когда вы пишете библиотеки, фреймворки или инструменты, которые должны быть надёжными даже в условиях ограниченного контроля над исходным кодом.
Если вам кажется, что я просто «развил идею», то вы, возможно, недооцениваете разницу между тем, чтобы пробить дырку в заборе , и тем, чтобы найти запасной ключ , который всегда был, но никто не проверял.
Инженерия — это не всегда решение проблемы. Иногда это подготовка решения к проблеме, которую вы ещё не видели.
Спасибо за ссылку — интересный подход с использованием C++20!
Я пока придерживаюсь C++17 по ряду проектных причин и личных предпочтений. На мой взгляд, переход на C++20 пока не даёт таких критических преимуществ, чтобы тянуть за собой всю инфраструктуру.
К тому же, в некоторых случаях старые добрые методы не только работают, но и проще читаются теми, кто ещё не "на ты" с новыми фишками стандарта.
Но я с интересом посмотрю на статью — всегда полезно видеть разные варианты решения задач. Возможно, для каких-то новых проектов или экспериментов это будет даже удобнее.
Да, думаю, что это станет отличным инструментом для тестовых фреймворков и других библиотек, которые полагаются на доступ к внутреннему устройству классов. Читал о рефлексии, но чувствовалось, что её возможности ограничены — особенно в плане доступа к приватным методам. Скорее всего, такие изменения сделают рефлексию гораздо более полноценной и гибкой.
Читая о mem-векторах, невольно ловишь себя на мысли, что это одна из тех технологий, которые могут либо перевернуть наше представление о работе с языковыми моделями, либо остаться интересной, но непрактичной идеей.
Главное преимущество, которое сразу бросается в глаза — это возможность кардинально увеличить объём информации, доступной модели. Вместо того чтобы дробить документы на кусочки, которые помещаются в ограниченное окно контекста, мы просто загружаем в модель несколько mem-векторов — и вот она уже "знает" содержимое целой библиотеки. Особенно занимает перспектива обновлять знания модели без её переобучения. Это могло бы решить одну из самых болезненных проблем современных LLM — их статичность после обучения.
Но вот что действительно заставляет задуматься — практическая реализация. Нынешний процесс создания mem-векторов трудоёмкий и медленный. Пока не будет найден способ быстро генерировать эти векторы (через специальный энкодер), технология останется уделом лабораторий.
Ещё вопрос: как поведёт себя модель, когда в её "памяти" окажутся противоречивые данные из разных mem-векторов? Не приведёт ли это к ещё большим проблемам с галлюцинациями?
В общем, технология действительно выглядит многообещающей.