Чтобы подсчитать ссылки, нужно ли мне загромождать свои API с помощью shared_ptr? - PullRequest
2 голосов
/ 03 февраля 2010

Недавно у меня была следующая ошибка памяти , которую легко обнаружить, но ее сложнее обнаружить в более сложном коде:

class Foo : public IFoo {
  const Bar& bar_;
public:
  Foo(const Bar& bar) : bar_(bar) {
  }
  void test() {
    // access bar_ here
  }
};

int baz() {
  IFoo* foo = NULL;
  if(whatever) {
    Bar bar;
    foo = new Foo(bar);
  }
  else {
    // create other foo's
  }
  foo->test(); // segmentation fault
}

Ошибка в том, что Bar немедленно выходит из области видимости, уничтожается и затем используется в foo->test(). Одним из решений является создание Bar в куче, используя Bar* bar = new Bar(). Однако я не люблю этого делать, потому что мне нужно держать указатель Bar* bar на верхнем уровне, чтобы я мог получить к нему доступ и delete в конце, даже если Bar является чем-то специфичным для этот конкретный кодовый блок if(whatever){}.

Другое решение - boost::shared_ptr<Bar>, но я не могу просто написать это:

  if(whatever) {
    boost::shared_ptr<Bar> bar(new Bar());
    foo = new Foo(*bar);
  }

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

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

(Кроме того, если вы используете эти указатели с подсчетом ссылок, вам следует начать беспокоиться о том, действительно ли вы хотите shared_ptr или, скорее, weak_ptr и т. Д.)

И что бы я использовал в качестве эквивалента Foo(const Bar& bar)? Foo(const shared_ptr<const Bar> bar)?

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

Ответы [ 6 ]

10 голосов
/ 03 февраля 2010

На самом деле я делаю везде использую shared_ptr ... Есть несколько способов сделать его менее загроможденным.Одно соглашение, которое я использую, это typedefs для каждого определенного класса:

class AClass {
public:
    typedef boost::shared_ptr<AClass> Ptr;
    typedef boost::weak_ptr<AClass> Ref;
    //...
};

Делает код намного более читабельным:)

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

2 голосов
/ 03 февраля 2010

Было бы лучше либо не передавать bar byref в Foo (другими словами, изменить конструктор Foos), либо скопировать его, чтобы у вас была копия в Foo. Конечно, в некоторых случаях это невозможно.

Что касается покрытия вашего кода с помощью shared_ptr<Bar>, самое простое решение - набратьdef shared_ptr<Bar> до BarPtr или аналогичный.

1 голос
/ 20 октября 2011

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

Не стоит беспокоиться, если вы следуете своему дизайну и понимаете, что такое weak_ptr (подсказка: это не ссылка) и для чего оно (подсказка: это не для «разрывных циклов»).

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

1 голос
/ 03 февраля 2010

Я бы действительно предложил другое решение ...

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

class IBar
{
public:
  virtual ~IBar() {}
  virtual IBar* clone() const;
};

class Foo: public IFoo
{
public:
  Foo(const IBar& ibar): m_bar(ibar.clone()) {}
  Foo(const Foo& rhs): m_bar(rhs.m_bar->clone()) {}
  Foo& operator=(const Foo& rhs) { Foo tmp(rhs); this->swap(tmp); return *this; }
  virtual ~Foo() { delete m_bar; }

  void swap(Foo& rhs) { std::swap(m_bar, rhs.m_bar); }

private:
  IBar* m_bar;
};

И теперь ваш код работает, потому что я сделал копию:)

Конечно, если бы Bar НЕ был полиморфным, тогда было бы намного проще.

Итак, даже если он не использует ссылку, вы уверены, что он вам действительно нужен?


Давайте теперь не будем следить, потому что есть ряд вещей, которые вы делаете неправильно:

  • foo = new Foo(bar) не очень, как вы собираетесь гарантировать, что память будет удалена в случае возникновения исключения или некоторого введения некоторого return в беспорядок if?Вам следует перейти к объекту, который управляет памятью auto_ptr или новому unique_ptr из C ++ 0x или Boost.
  • вместо использования подсчета ссылок, почему Foo не становится владельцем?Foo(std::auto_ptr<Bar> bar) тоже подойдет.

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

1 голос
/ 03 февраля 2010

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

Поскольку foo является самым долгоживущим объектом, как насчет создания экземпляра Bar внутри него и создания ссылок на другие переменные (bar в вашем коде)?

С другой стороны, если Bar действительно специфично для внутренней части блока if, test() вообще не нужно. В этом случае нет проблем с удержанием указателя внутри Foo и последующим удалением его в конце блока if. В противном случае, возможно, это не так специфично для блока, и вам придется немного переосмыслить свой дизайн.

0 голосов
/ 03 февраля 2010

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

int baz() {
  Bar bar;
  IFoo* foo = NULL;
  if(whatever) {
    foo = new Foo(bar);
  }
  else {
    // create other foo's
  }
  foo->test(); // no more segmentation fault
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...