Класс контейнера указателя, который не может быть скопирован по значению - PullRequest
2 голосов
/ 10 января 2009

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

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 ++, и ваше предложение очень ценится.

Спасибо

Ответы [ 7 ]

7 голосов
/ 10 января 2009

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

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

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

3 голосов
/ 10 января 2009

Что вы сделали, так это boost :: scoped_ptr

Пожалуйста, прочитайте комментарий от j_random_hacker.

2 голосов
/ 10 января 2009

Правильна ли эта реализация? Я сделал копию конструктора как частный ...

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

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

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

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

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

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

//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).

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

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

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

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

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

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

#include <boost/utility.hpp>

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

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

1 голос
/ 10 января 2009

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

  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 подробно описан в этом ответе.

1 голос
/ 10 января 2009

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

Что касается вашего второго вопроса, вы обходите вокруг delete , выдавая ошибку подтверждения, устанавливая pointer на какое-либо значение маркера после его первого удаления. Что-то вроде:

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

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

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

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

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

0 голосов
/ 10 января 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...