#c #memory #memory-management #smart-pointers
#c #память #управление памятью #интеллектуальные указатели
Вопрос:
Я пытаюсь самостоятельно реализовать интеллектуальный указатель. Я знаю, что могу использовать интеллектуальные указатели вместо этого, но я пытаюсь сделать это только для понимания структуры интеллектуальных указателей.
Проблема заключается в следующем, когда мой интеллектуальный указатель начинает вызывать деструктор, это проверяет, нет ли моего указателя, nullptr
тогда, если это правда, это приведет к удалению ptr.
После этого, когда деструктор снова вызывал приведения, я получаю исключение, потому что деструктор пытается удалить уже удаленный элемент, и мой оператор if во второй раз не работает (как я и ожидал), потому что после удаления элемента адрес меняется, а указатель не отображается.больше null.
Как я могу улучшить этот код и как я могу не удалять дважды уже удаленный указатель?
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <memory>
using std::cout;
using std::endl;
template<typename T>
class Smart_Pointer
{
private:
T* ptr;
public:
Smart_Pointer(T* ptr);
~Smart_Pointer();
Tamp; operator*();
};
template<typename T>
Smart_Pointer<T>::Smart_Pointer(T* ptr)
{
this->ptr = ptr;
}
template<typename T>
Smart_Pointer<T>::~Smart_Pointer()
{
if (ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
}
template<typename T>
Tamp; Smart_Pointer<T>::operator*()
{
return *ptr;
}
int main()
{
Smart_Pointer<int> castS(new int(10));
Smart_Pointer<int> castS2 = castS;
}
Комментарии:
1. Это не то, как работают интеллектуальные указатели.
2. Это не решает вопрос, но
delete ptr;
word просто отлично, еслиptr
это нулевой указатель; в этом нет необходимостиif (ptr != nullptr)
. И установкаptr = nullptr;
после удаления бессмысленна, потому что объект уничтожается иptr
больше не будет существовать.3.
Smart_Pointer(Smart_Pointer constamp;) = delete;
потому что созданный компилятором конструктор копирования не будет работать. ИSmart_Pointeramp; operator=(Smart_Pointer constamp;) = delete;
для оператора присваивания, по тем же причинам.
Ответ №1:
Интеллектуальный указатель — это обобщающий термин. Для библиотеки std, которую он описывает unique_ptr
, shared_ptr
и weak_ptr
.
Если вы хотите реализовать уникальный ptr, вам необходимо убедиться, что необработанный указатель принадлежит только одному уникальному ptr, поэтому вам нужно удалить конструктор копирования и оператор присваивания копирования. Чтобы иметь возможность передавать права собственности между вашими уникальными указателями, вы должны предоставить конструктор перемещения и оператор присваивания перемещения таким образом, чтобы право собственности передавалось.
Если вы хотите реализовать общий ptr, вам необходимо реализовать подсчет ссылок.
Как я могу удалить все указатели на один и тот же адрес? C
Это то, чего вы не хотите делать, вы хотите сохранить управляемый объект в рабочем состоянии до тех пор, пока существует хотя бы один общий указатель, владеющий этим управляемым объектом.
Правило трех: если для класса требуется определяемый пользователем деструктор, определяемый пользователем конструктор копирования или определяемый пользователем оператор присваивания копирования, почти наверняка требуются все три.
Правило пяти: поскольку наличие определяемого пользователем деструктора, конструктора копирования или оператора присваивания копирования предотвращает неявное определение конструктора перемещения и оператора присваивания перемещения, любой класс, для которого желательна семантика перемещения, должен объявлять все пять специальных функций-членов:
Ответ №2:
Как я могу удалить все указатели на один и тот же адрес? C
Вероятно, вы не можете сделать это надежно и автоматически. Помните о теореме Райса и читайте больше о программировании на C , затем смотрите эту ссылку на C . Поймите, что указатели организуют ваше виртуальное адресное пространство в виде некоторого ориентированного графа, который развивается во время выполнения вашей программы.
Может быть, вы хотите очистить все указатели на один и тот же адрес.
Затем подробнее о сборке мусора.
Например, прочитайте руководство по GC .
Рассмотрите возможность использования какого-либо инструмента статического анализа в вашем исходном коде C , например, статического анализатора Clang.
Подумайте также о создании некоторого вашего кода на C (например, SWIG или GNU bison). Вы можете закодировать свой генератор кода C (например, используя GPP или GNU m4, или свой собственный генератор файлов C ), чтобы упростить управление вашими указателями.
Прочитайте также n3337 (некоторый проект стандарта C ) и документацию вашего компилятора C (возможно, GCC).
Имейте в виду, что подсчет ссылок имеет недостатки (например, он не поддерживает многопоточность).
Изучите для вдохновения исходный код существующих программ с открытым исходным кодом C (например, на github), таких как Fish, Qt, RefPerSys, GCC, Clang, ANTLR. Рассмотрите возможность внесения вклада в один из них.
Ответ №3:
Если вы пытаетесь эмулировать поведение unique_ptr , это решается простым запретом копирования. Если вы удалите конструктор копирования, никакие два интеллектуальных указателя не могут указывать на один и тот же адрес одновременно. Вы просто пишете:
SmartPtr(const SmartPtramp;) = delete;
Однако, если вы сделаете это, вам может понадобиться способ передачи права собственности, поэтому было бы неплохо реализовать конструктор перемещения:
SmartPtr(SmartPtramp;amp; other) {
ptr = std::exchange(other.ptr, nullptr);
}
Или что-то в этом роде. Если вы действительно хотите, чтобы два интеллектуальных указателя указывали на один и тот же адрес, вам нужен способ решить, какой из них будет удалять ptr, обычно последний, который выходит из области видимости. Способ, которым это делается в стандарте (например. В shared_ptr) заключается в определении общей структуры между всеми экземплярами класса, но это, вероятно, выходит за рамки этого ответа..
Комментарии:
1. Я исправил это. Однако это не так, просто отличается от стандартного
2. Что вы подразумеваете под «отличным от стандартного»? Это UB, потому что вы читаете неинициализированную переменную и
delete
должны произойти сбои, если указатель не окажется равным нулю.3. Я вижу, вы правы. Я думал переместить назначение.
Ответ №4:
Как я могу удалить все указатели на один и тот же адрес?
Первая проблема заключается в том, что вы не знаете, сколько точек на один и тот же адрес.
Это можно решить многими способами.
- Используя блок управления (например, std::shared_ptr), все интеллектуальные указатели указывают на блок управления, который указывает на реальный объект и имеет счетчик.
- Используя навязчивый счетчик в указателе на объект, либо наследуйте его, либо объект должен быть унаследован счетчиком.
- Использование внешнего свободного счетчика.
- Проанализируйте всю память, чтобы увидеть, указывает ли что-нибудь на этот адрес (сборка мусора, GC).
Счетчик должен быть атомарным, если вы намерены выполнять многопоточность.
Реализация внешнего свободного счетчика имеет огромное преимущество в том, что у вас нет косвенности 1. и у вас нет навязчивости 2 или сложности GC.
Непроверенный код
std::unordered_map<void *, std::atomic_int> extro_count;
Затем вам нужно реализовать правило 3 или правило 5 в зависимости от того, насколько продвинутым вы хотите его сделать.
template<typename T>
Smart_Pointer<T>::~Smart_Pointer() {
if (ptr != nullptr) {
if (--extro_count[ptr] == 0) {
extro_count.erase(ptr);
delete ptr;
ptr = nullptr;
}
}
Я оставляю реализацию остальных в OP.
Это все еще очень небезопасно в потоковом коде, если вы не будете осторожны.