Новый универсальный указатель any_ptr (теперь dumb_ptr), чтобы сделать код более удобным для повторного использования среди умных указателей - PullRequest
3 голосов
/ 16 марта 2011

В последнее время я использовал много разных умных указателей для повышения, а также обычные указатели. Я заметил, что по мере развития вы склонны понимать, что вам приходится переключать типы указателей и механизм управления памятью, потому что вы пропускаете некоторую циклическую зависимость или какую-то небольшую раздражающую вещь. Когда это происходит и вы меняете тип указателя, вам нужно либо пойти и изменить целую кучу сигнатур ваших методов, чтобы взять новый тип указателя, либо на каждом сайте вызова вы должны конвертировать между типами указателей. У вас также есть проблема, если у вас есть та же функция, но вы хотите, чтобы она принимала несколько типов указателей.

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

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

Моя идея - написать новый класс any_ptr<T> с конструкторами преобразования из всех основных типов указателей, скажем, T*, shared_ptr<T>, auto_ptr<T>, scoped_ptr<T>, возможно, даже weak_ptr<T>, и затем иметь его выставить операторы * и ->. Таким образом, его можно использовать в любой функции, которая не возвращает указатель вне функции и может вызываться с любой комбинацией общих типов указателей.

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

РЕДАКТИРОВАТЬ 1

Прочитав ваши ответы, я хотел бы сделать несколько заметок, которые были слишком длинными для комментариев.

Во-первых относительно использования необработанных указателей или ссылок (@shoosh). Я согласен, что вы могли бы заставить функции использовать необработанные указатели, но затем предположите случай, когда я использовал shared_ptr, что означает, что мне пришлось бы идти ptr.get () на каждом сайте вызова, теперь предположим, что я понимаю, что сделал циклическую ссылку и Я должен изменить указатель на weak_ptr, затем я должен пойти и изменить все эти сайты вызовов на x.lock (). Get (). Теперь я согласен, что это не катастрофа, но это раздражает, и я чувствую, что есть элегантное решение для этого. То же самое можно сказать и для передачи ссылок T & T и перехода * x, аналогичные изменения сайта вызовов должны быть сделаны.

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

Во-вторых в отношении семантики smart_ptr: я согласен, что разные интеллектуальные указатели используются по разным причинам и имеют определенные соображения, которые необходимо учитывать при копировании и хранении (вот почему boost::shared_ptr<T> не конвертируется автоматически в T*).

Однако я предполагал, что any_ptr (может быть, плохое имя в ретроспективе) будет использоваться только в тех случаях, когда указатель не будет сохранен (в чем-либо, кроме, возможно, временных переменных в стеке). Он должен быть просто неявно конструируемым из различных типов интеллектуальных указателей, перегружать операторы * и -> и быть преобразованным в T * (через пользовательскую функцию преобразования T*()). Таким образом, семантика any_ptr точно такая же, как и T*. И как таковой, он должен использоваться только там, где было бы безопасно использовать необработанный ptr (это то, что @ Alexandre_C говорил в комментарии). Это также означает, что не будет ни одной из «тяжелых машин», о которых говорил @Matthieu_M.

В-третьих в отношении шаблонов. Хотя шаблоны хороши для чего-то, я опасаюсь их по причинам, изложенным выше.

ЗАКЛЮЧИТЕЛЬНО : В общем, я пытаюсь сделать это для функций, в которых в качестве параметров обычно используется raw ptr (T *). Я хотел бы создать систему, в которой эти параметры могут автоматически принимать любыеразличных типов smart_ptr без необходимости выполнять преобразования на сайтах вызовов.Причина, по которой я хочу это сделать, заключается в том, что я думаю, что это сделает код более читабельным, исключив излишнюю конверсию (и, следовательно, также немного короче, хотя и ненамного), и это сделает рефакторинг и попытки использования различных режимов интеллектуальных указателей менее хлопотными.

Возможно, мне следовало бы назвать это unmanaged_ptr вместо any_ptr.Это было бы более правильно описать семантику.Я прошу прощения за грязное имя.

РЕДАКТИРОВАТЬ 2

Хорошо, вот класс, который я имел в виду.Я назвал его dumb_ptr.

template<typename T>
class dumb_ptr {
 public:
  dumb_ptr(const dumb_ptr<T> & dm_ptr) : raw_ptr(dm_ptr.raw_ptr) { }  
  dumb_ptr(T* raw_ptr) : raw_ptr(raw_ptr) { }  
  dumb_ptr(const boost::shared_ptr<T> & sh_ptr) : raw_ptr(sh_ptr.get()) { }  
  dumb_ptr(const boost::weak_ptr<T> & wk_ptr) : raw_ptr(wk_ptr.lock().get()) { }  
  dumb_ptr(const boost::scoped_ptr<T> & sc_ptr) : raw_ptr(sc_ptr.get()) { }  
  dumb_ptr(const std::auto_ptr<T> & au_ptr) : raw_ptr(au_ptr.get()) { }  
  T& operator*() { return *raw_ptr; }
  T * operator->() { return raw_ptr; }
  operator T*() { return raw_ptr; }
 private:
  dumb_ptr() { } 
  dumb_ptr<T> operator=(const dumb_ptr<T> & x) { }
  T* raw_ptr;
};

Он может автоматически конвертировать из общих интеллектуальных указателей и может рассматриваться как необработанный указатель T *, в дальнейшем он может быть автоматически преобразован в T *.Конструктор по умолчанию и оператор присваивания (=) были скрыты, чтобы удержать людей от использования его для чего-либо, кроме аргументов функции.При использовании в качестве аргумента функции может быть сделано следующее:

void some_fn(dumb_ptr<A> ptr) {
  B = ptr->b;
  A a = *ptr;
  A* raw = ptr;
  ptr==raw;
  ptr+1;
}

Это почти все, что вы хотели бы сделать с указателем.Он имеет ту же семантику, что и необработанный указатель T*.Но теперь его можно использовать с любым интеллектуальным указателем в качестве параметра без необходимости повторять код преобразования (.get, .lock) на каждом сайте вызова.Кроме того, если вы измените свои умные указатели, вам не придется исправлять каждый сайт вызовов.

Теперь я думаю, что это достаточно полезно, и я не вижу проблем с этим?

Ответы [ 5 ]

2 голосов
/ 16 марта 2011

С таким классом any_ptr вы не сможете делать практически ничего, кроме * и ->. Нет назначения, копирования конструкции, дублирования или уничтожения. если это все, что вам нужно, то просто напишите функцию, принимающую в качестве аргумента необработанный указатель T*, а затем вызовите ее, используя .get() или еще много чего в вашем автоматическом указателе.

2 голосов
/ 16 марта 2011

Вы можете использовать разные умные указатели, потому что они предлагают различную семантику и компромиссы.

Какая семантика any_ptr?Если оно исходит от unique_ptr / scoped_ptr, его не следует копировать.Если речь идет о shared_ptr или weak_ptr, то для подсчета ссылок потребуется их тяжелая техника.

У вас был бы тип со всеми недостатками (тяжелый, не копируемый) для небольшого усиления ...


Проблема в интерфейсе.Если только методы не управляют временем жизни объекта (в этом случае требуется точный тип указателя), ему не нужно знать, как управляется это время жизни.

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

  • void foo(T* ptr);, который называется shared_ptr<T> p; foo(p.get());
  • void foo(T& ptr);который называется foo(*p);

Примечание: второй интерфейс гораздо более независим от указателей

Это простая инкапсуляция: метод должен знать только оминимум, он должен работать.Если он не управляет временем жизни, то раскрытие способа управления этим временем жизни ... вызывает те колебания, которые вы наблюдали.

1 голос
/ 16 марта 2011

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

Мне интересно, существует ли уже общий способ иметь дело, то есть написать методы, которые являются независимыми от тип указателя вы передаете ему?

Вы почти ответили на свой вопрос.

Смарт-указатель должен присутствовать в сигнатурах функций, только если передается право собственности на объект. Если функция не становится владельцем объекта, она должна принять его с помощью простого указателя или ссылки. Интеллектуальным указателем следует принимать только те функции, которым необходимо владеть объектом или продлить его время жизни.

1 голос
/ 16 марта 2011

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

virtual void MyMethod(MyClass* ptr)
{
    ptr->doSomething();
}

и сделать

MyObj->MyMethod(mySmartPtr.get());

на сайте вызова?

Вы можете даже передать ссылку:

virtual void MyMethod(const MyClass& ptr)
{
    ptr->doSomething();
}

и выполнить

MyObj->MyMethod(*mySmartPointer);

, которая имеет преимущество в том, что почти всегда имеет один и тот же синтаксис (за исключением std::weak_ptr).

В противном случае типы интеллектуальных указателей становятся стандартными: std::unique_ptr, std::shared_ptr и std::weak_ptr имеют свое применение.

Интеллектуальные указатели предназначены для хранения объектов согласнок определенной семантике управления ресурсами .Вы можете почти всегда извлекать из них пустой указатель.

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

Для этой цели вы можете добавить typedefs:

struct MyClass
{
    typedef std::shared_ptr<MyClass> Handle;
    static Handle CreateHandle(...);

    ...

private:
    Handle internalHandle;
    void setHandle(const MyClass::Handle& h);
};

и сделать

void MyClass::setHandle(const MyClass::Handle& h)
{
    this->internalHandle = h;
}
0 голосов
/ 06 апреля 2011

Отвечая на мой собственный вопрос, чтобы он не появлялся в списке без ответа. По сути, ни один из других ответов не нашел то, что я считал серьезным недостатком в моей идее. Попросите @Alexandre C. понять, что я пытался сделать.

...