Класс контейнера указателя, который нельзя скопировать по значению

Мне нужен умный указатель для моего проекта, который можно отправить в несколько методов в качестве параметра. Я проверил auto_ptr и shared_ptr от буста. Но ИМО, это не соответствует моим требованиям. Ниже приведены мои выводы

auto_ptr: при передаче другому методу право собственности будет передано, а базовый указатель будет удален, когда область действия этого метода закончится. Мы можем обойти это, передав auto_ptr по ссылке, но не существует механизма времени компиляции, гарантирующего, что он всегда передается по ссылке. Если по ошибке пользователь забыл передать ссылку, возникнут проблемы.

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

Поэтому я решил написать тривиальный класс контейнера с шаблонным указателем, который нельзя скопировать по значению, и позаботился об удалении базового указателя. Вот

template <typename T>
class simple_ptr{
public:
    simple_ptr(T* t){
       pointer = t;
    }
    ~simple_ptr(){
       delete pointer;
    }
    T* operator->(){
       return pointer;
    }
private: 
    T* pointer;
    simple_ptr(const simple_ptr<T>& t);
};

Это правильная реализация? Я сделал конструктор копирования закрытым, чтобы компилятор предупреждал, когда кто-то пытается передать его по значению.

Если случайно указатель удален, операция удаления деструктора вызовет ошибку утверждения. Как я могу это исправить?

Я новичок в C++ и очень ценю ваше предложение.

Спасибо

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
465
7

Ответы 7

Чтобы ответить на ваш первый вопрос, лучшая гарантия того, что это правильно, - это реализовать вокруг него тестовую оснастку, чтобы выполнить некоторую простую задачу, и убедиться, что вы получаете ожидаемое поведение. Для меня это гораздо больше утешения, чем то, что код правильный, чем мнение какого-то случайного человека, читающего его.

Что касается вашего второго вопроса, вы работаете с Удалить, выдающим ошибку утверждения, устанавливая указатель на некоторое значение маркера после его удаления в первый раз. Что-то вроде:

if (pointer) {
    delete pointer;
    pointer = NULL;
} else {
    error("Attempted to free already freed pointer.");
}

Проблема, с которой вы здесь столкнетесь, заключается в том, что ваш перегруженный оператор -> возвращает значение указателя, а это означает, что любой, кому вы его вернете, также может вызвать Удалить, в результате чего проверка, которую я предлагаю выше, не сработает. Например:

simple_ptr<int> myPtr = someIntPointer;
...
delete myPtr.operator->(); /* poof goes your pointered memory region */

Я мог бы порекомендовать вам не полагаться на перегрузку оператор->, а просто потребовать, чтобы те, кто использует этот класс, вызывали метод, который разыменовывает указатель внутри, прежде чем передавать это значение обратно пользователю.

Надеюсь это поможет.

Спасибо за ответ. Но установка значения маркера не сработала. Это все еще выдает ошибку.

Navaneeth K N 10.01.2009 10:25

Is this implementation correct? I have made copy constructor as private ...

Вы можете сделать то же самое для оператора присваивания:

simple_ptr& operator=(const simple_ptr<T>& t);

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

const T* operator->() const { return pointer; }
const T& operator*() const { return *pointer; }
T& operator*() { return *pointer; }

If by chance the pointer is deleted, delete operation on the destructor will throw assertion error. How can I workaround this?

Вы имеете в виду, если я сделаю это:

//create instance
Car* car = new Car;
//assign to smart ptr
simple_ptr<Car> ptr(car);
//explicit delete
delete car;
//... assertion when ptr is destroyed ...

Способ (я не знаю, является ли это способ хорошо), чтобы предотвратить это, состоит в том, чтобы объявить конструктор и / или деструктор и / или оператор удаления класса T как private и сказать, что simple_ptr является friend из класс T (так что только класс simple_ptr может создавать и / или уничтожать и / или удалять экземпляры T).

Marking the new operator as private in T class seems to be impossible as I have to modify all the classes which will be used with simple_ptr

Да, это правда: чтобы сделать мое предложение непосредственно выше, вам нужно будет изменить определения классов.

Если ваш вопрос: «как я могу сделать невозможным двойное удаление, не изменяя определения классов?» тогда я думаю, что ответы таковы:

  • Вы не можете: нужно быть осторожным в коде приложения (которое использует эти классы)
  • Вы можете: предоставить свой собственный менеджер кучи, то есть вашу собственную реализацию глобального оператора new и глобального оператора delete, и в вашем коде smart_ptr опросить ваш менеджер кучи, чтобы узнать, выделено ли это воплощение этого указателя, прежде чем вы его удалите.

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

Navaneeth K N 10.01.2009 10:33

Иногда я видел использование простых макросов DISABLE_COPY:

#define DISABLE_COPY(Class) \
        Class(const Class &); \
        Class &operator=(const Class &);

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

Разве вам не нужно включать "private:" перед этими строками? (Хотя это означало бы неожиданное изменение режима доступа для более поздних операторов ... К сожалению, нет способа «протолкнуть» и «вытолкнуть» текущий режим доступа к члену.)

j_random_hacker 10.01.2009 11:30

Трудно превзойти boost :: noncopyable. Без private это заменяет сгенерированные компилятором реализации по умолчанию неопределенными общедоступными объявлениями. Это все еще ошибка компоновщика. Добавьте к их аргументам «volatile», и вы поймаете 99% во время компиляции.

MSalters 12.01.2009 13:29

Вы сделали boost :: scoped_ptr

Также прочтите комментарий j_random_hacker.

Да, и, пожалуйста, используйте boost :: scoped_ptr вместо вашей собственной реализации просто потому, что сверхразумные мастера C++ потратили много времени на ее реализацию и убедились, что она ведет себя так, как ожидалось.

j_random_hacker 10.01.2009 11:23

Пожалуйста, используйте boost :: scoped_ptr <>, как предложил Мартин Йорк, потому что он:

  • Делает именно то, что вы хотите (это не копируемый указатель)
  • Не имеет накладных расходов выше, чем у стандартного указателя C
  • Был тщательно разработан сверхразумными мастерами C++, чтобы убедиться, что он ведет себя так, как ожидалось.

Хотя я не вижу никаких проблем с вашей реализацией (после применения изменений, предложенных ChrisW), у C++ много темных углов, и я не удивлюсь, если есть какой-то неясный угловой случай, который вы, я и другие здесь не смогли место.

boost :: scoped_ptr также имеет то преимущество, что он никогда не пытается удалить указатель на неполный тип класса - он использует boost :: checked_delete вместо простого удаления. Это особенно полезно при использовании частной переменной-члена scoped_ptr для хранения деталей реализации.

Doug 10.01.2009 12:05

Ух ты. Это как раз тот случай, о котором я говорю. Спасибо, Дуг!

j_random_hacker 10.01.2009 12:22

У вас есть два варианта:

  1. boost::scoped_ptr уже подробно описан j_random_hacker, потому что он не копируемый (не разделяет права собственности, как shared_ptr) и не перемещается (не передает право собственности, как auto_ptr. Auto_ptr имеет конструктор копирования, но он не копирует. Он перемещает оригинал указатель на * this). boost::scoped_ptr - это именно то, что вам нужно.
  2. const auto_ptr не допускает передачу права собственности. И возьмите свой параметр по ссылке на const (auto_ptr<T> const&). Если вместо этого автор функции принимает значение по значению, он все равно не будет работать, если вы попытаетесь передать const auto_ptr, потому что его конструктору копирования требуется неконстантный auto_ptr.

До C++ 1x, boost :: scoped_ptr - лучший выбор для ваших нужд или const auto_ptr, если вам нужно использовать официальные стандартные вещи (прочтите это). В C++ 1x вы можете использовать std::unique_ptr как лучшую альтернативу auto_ptr, потому что вам нужно явно указать, когда вы хотите передать право собственности. Попытка скопировать его приведет к ошибке времени компиляции. unique_ptr немного подробно описан в ответе это.

О нет. Когда они начали называть это 1x?

fizzer 10.01.2009 18:17

они начали так называть это в прошлом году после какой-то встречи - будем надеяться, что они закончат его в 2010 году :)

Johannes Schaub - litb 11.01.2009 06:54

Вы должны использовать boost :: scoped_ptr <>, как уже упоминалось.

В общем случае, если вам нужно сделать класс не копируемым, вы должны унаследовать от boost :: noncopyable, т.е.

#include <boost/utility.hpp>

class myclass : boost::noncopyable
{
    ...
};

Это делает всю работу по предотвращению копирования и хорошо самодокументируется.

Другие вопросы по теме