Способы облегчения работы с общими указателями в C ++ - PullRequest
1 голос
/ 21 декабря 2010

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

Я разработал несколько хитростей, чтобы упростить работу с общими указателями, например: используя typedef внутри класса, например:

class X
   {
   public:
      ...
      typedef std::shared_ptr<X> Ptr;
   };

Таким образом, вы можете легко написать X:: Ptr, который легче написать везде, чем "std :: shared_ptr".

Я также заметил некоторые недостатки для общих указателей:

  • Везде, где я использую общий указатель, мне нужноinclude <memory>
  • Я не могу больше использовать прямое объявление, если я просто хочу использовать указатель

Есть ли какие-либо другие приемы, с которыми проще работать с общими указателями?

Ответы [ 5 ]

8 голосов
/ 21 декабря 2010

DO NOT!

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

Общие указатели не являются серебряной пулей. Они становятся утечками памяти, как только у вас появляются циклические ссылки (и если вы планируете использовать их везде , они появятся довольно быстро)

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

Но по умолчанию используется объект, принадлежащий одному другому объекту. Он принадлежит функции и должен быть уничтожен, когда эта функция вернется, или он принадлежит классу, или как угодно еще. Часто вам не нужны указатели вообще. объект хранится по значению в std::vector, возможно. Или это просто локальная переменная или член класса. Если является указателем, его часто лучше выразить как scoped_ptr или, возможно, тот, который разрешает передачу права собственности (unique_ptr или auto_ptr).

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

Действительно, лучший подход состоит в том, чтобы избегать указателей как можно больше. Когда вам do нужен указатель, используйте тот, который имеет максимально возможную семантику владения (предпочитайте scoped_ptr, который вообще не допускает передачи владения, затем, если вам это нужно, используйте один что позволяет вам переносить владение, например unique_ptr, и только в качестве крайней меры следует использовать shared_ptr, что позволяет свободно делиться собственностью между любым количеством клиентов.

Нет волшебной палочки, которую вы можете помахать, чтобы ваш код на C ++ "просто работал". Единственный способ добиться этого - написать хороший твердый код на C ++. И вы делаете это, зная и используя имеющиеся в вашем распоряжении инструменты, а не притворяясь, что «эй, shared_ptr - это как сборщик мусора, не так ли? если я использую это ".

3 голосов
/ 21 декабря 2010

Не нужно просто выбирать один умный указатель для использования везде. Не каждый винт является головкой Филлипса.

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

struct X;

...
std::shared_ptr<X> ptr;  // legal

struct X {};
ptr = std::shared_ptr<X>(new X); // legal

Это второй раз, когда я слышал это заблуждение о SO, и оно просто на 100% неверно.

1 голос
/ 21 декабря 2010

В коде довольно часто встречается «соглашение», которому вы затем следуете, и если оно выполняется последовательно в вашей команде и т. Д., Люди смогут понять его.

Гораздо лучше объявить ваши совместно используемые указатели typedefs в "прямом" файле. Примерно так:

namespace boost { template< typename T > class shared_ptr; }

namespace MyStuff
{
   class Foo;
   class Bar;
   class Baz;

   typedef boost::shared_ptr<Foo> FooPtr;
   typedef boost::shared_ptr<Bar> BarPtr;
   typedef boost::shared_ptr<Baz> BazPtr;

}

Иногда вы захотите использовать другие указатели, чем shared_ptr, но тогда вы будете использовать другую нотацию с XPtr, что означает shared_ptr.

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

Я думаю, что мы используем FooWeakPtr для обозначения weak_ptr<Foo> FooConstPtr в значении shared_ptr<const Foo>

например.

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

1 голос
/ 21 декабря 2010

Что касается typedef, вы должны взглянуть на этот вопрос , который касается точно такой же проблемы.

Во второй части, я полагаю, вы ошибаетесь как std::shared_ptrможет использоваться для неполных типов, как указано в 20.9.10.2/2 N3225:

Параметр шаблона T из shared_ptr может быть неполным типом.

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

0 голосов
/ 21 декабря 2010

Я предпочитаю такой подход к нему:

   namespace Internal
   {
      template <class T>
      struct DeclareShared
      {
         typedef std::shared_ptr<T> type;
      };

      template <class T>
      struct DeclareUnique
      {
         typedef std::unique_ptr<T> type;
      };
   }

   // Inherit one of these classes to use a generic smart pointer interface.

   // If class is abstract, use this interface.
   template <class T>
   struct SharedAbstract
   {
      typedef typename Internal::DeclareShared<T>::type APtr;
   };

   template <class T>
   struct Shared
   {
      typedef typename Internal::DeclareShared<T>::type Ptr;

      template <class... P>
      static Ptr shared(P&&... args) 
      { 
         return std::make_shared<T>(std::forward<P>(args)...); 
      }
   };

   template <class T>
   struct UniqueAbstract
   {
      typedef typename Internal::DeclareUnique<T>::type AUPtr;
   };

   template <class T>
   struct Unique
   {
      typedef typename Internal::DeclareUnique<T>::type UPtr;

      template <class... P>
      static UPtr shared(P&&... args) 
      { 
         return std::unique_ptr<T>(new T(std::forward<P>(args)...)); 
      }
   };

Добавлено больше интерфейсов для области видимости и т. Д., И все, что вы хотите, вы можете сделать, например:

struct Foo : public Shared<Foo>, public Unique<Foo>
{};

Foo::Ptr foo = Foo::shared();
Foo::UPtr unique = Foo::unique();

Я не уверен, как VS10 справляется с вариадическими шаблонами, но по крайней мере это работает на GCC4.5 +.

...