Какова лучшая стратегия для общих указателей typedef? - PullRequest
20 голосов
/ 13 декабря 2010

У меня быстрый вопрос по поводу использования typedefs для длинных шаблонов.Суть: я оказался в чем-то вроде рассола - кажется, нет хорошего места для размещения typedef, кроме локальных для клиентских функций.Хотя есть похожие SO вопросы (см., Например, здесь ), ни один из них, похоже, не решает это точно.Обратите внимание, что этот вопрос не затрагивает вопрос о том, являются ли typedefs желательными в дальнейшем: я попытался упростить вещи для пояснения.

Моя проблема возникла при работе с boost::shared_ptr<T>.По сути, я хочу сделать следующее:

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<Widget> WidgetPtr;

Размещение этого typedef в заголовке объявления Widget кажется уродливым.Кажется, здесь есть два соображения: (i) если Widget сам по себе не использует общие указатели в своих членах, мы добавили дополнительное включение (поскольку мы не можем переслать объявление класса шаблона boost::shared_ptr -поправьте меня, если я ошибаюсь?) (ii) если мы хотим использовать этот typedef во время объявления другого класса (вызов этого класса Foo), мы нарушаем лучшие практики, добавляя Widget.h вместо простого объявления вперед Widget или включительно WidgetFwd.h ... если только этот typedef не повторяется в последнем.Кроме того, кажется, не имеет смысла вводить определение boost::shared_ptr<Widget> во время объявления самого Widget - мы, кажется, смешиваем объявление Widget с ожиданием того, как клиенты будут использовать интерфейс Widget.

Ладно, это плохо, но это хуже: если я не попробую какую-либо комбинацию из вышеперечисленного, я получу дубликаты typedef в клиентском коде, что приводит к несогласованности (и, следовательно, вероятно, к ошибке)весь смысл в том, что с учетом Widget, * typedef WidgetPtr должен действовать как тип сам по себе.Пример: мы не хотим, чтобы Foo использовал один WidgetPtr, typedef равный boost::shared_ptr, в то время как Bar использует WidgetPtr в качестве typedef для std::auto_ptr.

Другой метод (и один из немногих, о которых я упоминал в онлайн-обсуждении), - сделать typedef общедоступным членом Widget, а затем использовать Widget::Ptr:

class Widget {
// ...
public:
     typedef boost::shared_ptr<Widget> Ptr;
};

Опять же, я некак, например, (i) это говорит о том, что тип указателя каким-то образом является членом класса, и (ii) это приводит к непростому интерфейсу.Что еще хуже: поскольку каждый класс, который я пишу, может указывать на использование умных указателей, я в конечном итоге гонюсь за хвостом воображаемого клиента.Гадкий, уродливый, уродливый.

В его нынешнем виде я удалил определения типов из этой кодовой базы (поскольку они привели к серьезной путанице, дублированию) и повторно ввел их локально в выбранные функции.Здесь опять есть проблема с непоследовательным использованием, но она не настолько серьезна.

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

Я что-то упускаю из виду или это просто сложно?

PS - Извинения за длину вышеупомянутого;Я не мог найти более простой способ полностью выразить проблему.

Ответы [ 7 ]

7 голосов
/ 13 декабря 2010

Более того, кажется, не имеет смысла вводить typedef boost :: shared_ptr во время объявления самого Widget - мы, кажется, смешиваем объявление Widget с ожиданием того, как клиенты будут использовать интерфейс Widget.

Прежде всего, это совсем не так - в конце концов, способ, которым клиенты будут (и могут) использовать интерфейс , является частью самого интерфейса; и для C ++, не будучи сборщиком мусора, управление памятью объектов является довольно важной частью их интерфейса.

Итак, есть два случая. В одном случае Widget будет ожидать, что он будет использоваться через общий указатель. Это будет означать, что, например. Дочерние виджеты, полученные из виджета, возвращаются как shared_ptr s, у каждого созданного виджета есть shared_ptr и так далее. Было бы вполне законно вводить определение WidgetPtr в том же заголовке, что и Widget.

Во втором случае ожидается управление Widget s, например. обычными new и delete. Клиенты могут использовать shared_ptr s в особых случаях, но ничего не говорит, например. процедура диалога принтера не может использовать auto_ptr вместо этого. Клиенты должны быть готовы к тому, что если wptr - это shared_ptr, строка

shared_ptr<Widget> w2(wptr->firstChild()->parent());

приводит к катастрофе.

Ваш вопрос, кажется, указывает на то, что последнее - ваше дело. Итак, ИМХО, то, что вы сделали, в порядке. Клиенты могут выбирать средства управления Widget объектами, если это не влияет на других клиентов.

6 голосов
/ 13 декабря 2010

Мне не нравится библиотека, диктующая использование определенного умного указателя, но я терплю это, если это необходимо.

Если вы хотите заставить пользователей всегда использовать shared_ptr для управления виджетом, это невозможно, поэтому даже не пытайтесь.

С другой стороны, если у вас есть метод из Widget, который возвращает boost::shared_ptr<Widget>, то предоставление (разумной) typedef может упростить клиентский код.

Поэтому я бы посоветовал использовать внутренний typedef:

class Widget
{
public:
  typedef boost::shared_ptr<Widget> Ptr;

  Ptr AccessFirstChild();
}; // class Widget

В этом случае вполне нормально #include необходимые заголовки.

5 голосов
/ 13 декабря 2010

Вы думаете об этом, по моему мнению.Каждый, кто хочет получить shared_ptr<Widget>, в любом случае должен будет включить заголовочный файл виджета.Помещение typedef (это хорошая идея imo) в Widget.h имеет 100% смысл для меня.

3 голосов
/ 04 сентября 2013

Мой подход (с использованием типов нижней черты, просто потому, что я так делаю)

class Type
{
    public:
        typedef shared_ptr<Type>        ptr;
        typedef shared_ptr<const Type>  const_ptr;
};

Я считаю, что версия const_ptr чертовски полезна.

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

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

#include <iostream>
#include <memory>

template <class T>
struct SharedVirtual
{
   typedef std::shared_ptr<T> VPtr;
};

template <class T>
struct Shared
{
   typedef std::shared_ptr<T> Ptr;

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

class Foo : public SharedVirtual<Foo>
{
   public:
      virtual void foo() const = 0;
};

class Test : public Foo, public Shared<Test>
{
   public:
      void foo() const { std::cout << "Hai u!" << std::endl; }
};

void print(const Foo::VPtr& ptr)
{
   ptr->foo();
}

int main()
{
   auto ptr = Test::ptr();
   print(ptr);
}
1 голос
/ 13 декабря 2010

Я использовал для структурирования своего кода C ++ в библиотеки.У библиотеки будет куча заголовков для потребления клиентом, и все это внутри каталога include/LibraryName.Кроме того, у меня был бы один заголовок с именем Fwd.h внутри этого каталога с предварительными объявлениями всех классов вместе с указателями typedefs.

Кроме того, каждый общедоступный заголовок будет включать Fwd.h, чтобы включение заголовка автоматическидать вам все предварительные объявления и указатели typedefs.Это сработало очень хорошо на практике.

Не все классы необходимо помещать в shared_ptr.Я бы создавал указатели typedefs только для типов, которые я ожидал создать динамически, и в этом случае я бы поставлял фабрику.Это дает дополнительное преимущество: вы можете избавиться от предоставления клиентского кода только с типами интерфейса и скрыть конкретную реализацию в каталоге src вашей библиотеки.Не совсем то, что вы просили совета, но это дает полную картину моего метода.И, наконец, можно также указать вспомогательный заголовок LibraryName.h, который включает Fwd.h и все другие публичные заголовки.

Удачи!

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

Вторая часть первая: используйте пространство имен, т. Е .:

namespace WidgetStuff {
  class Widget { ..
  typedef shared_ptr<Widget> WidgetPtr;
  ..

Если вы хотите разделить это:

namespace WidgetStuff {
   class Widget { ...
}
...
namespace WidgetStuff { 
  typedef ...

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

А теперь ответили и на первую часть, если вы решите:

#include <widget.h>
#include <widget_utils.h>

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

...