Конкретная проблема управления памятью в C ++ - PullRequest
1 голос
/ 23 января 2012

Представьте себе следующие классы в java или C #:

class A
{
  B b;
  //some other stuff

  public A(B b) {this.b = b;}
}

class B
{
  A createA() {return new A(this); }
}

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

A complicatedCreateA()
{
  B = new B();
  return b.createA();
}

, и виртуальная машина / CLR обеспечит защиту от утечкиобъем памяти.

Как я могу реализовать подобный шаблон в C ++, чтобы не пропускать память и ссылаться на очищенные ресурсы?

РЕДАКТИРОВАТЬ: Чтобы сделать это более ясным, я особенно беспокоюсь о том, что произойдет, если явызывать createA () более одного раза, и когда разные объекты A будут иметь разное время жизни, например:

A anotherMethod()
{
  B = new B();
  A a = b.createA();
  //use a locally, or use with yet another object C, etc.
  return b.createA();
}

У меня есть базовое понимание того, как умные указатели работают в C ++.Однако, даже если я сделаю что-то вроде:

boost::shared_ptr<B> b(new B());

, у меня нет доступа к этому интеллектуальному указателю изнутри B, поэтому я не могу передать его A. А как иначе A может убедитьсячто соответствующий объект B удаляется не слишком поздно и не слишком рано?

Ответы [ 5 ]

3 голосов
/ 23 января 2012

Точный эквивалент немного усложняется:

class A
{
    std::shared_ptr<B> b;
    //some other stuff

public:
    A(std::shared_ptr<B> const & b) : b(b) {}
};

class B : public std::enable_shared_from_this<B>
{
public:
    A createA() {return A(shared_from_this());}
};

A complicatedCreateA()
{
    std::shared_ptr<B> b = std::make_shared<B>();
    return b->createA();
}

Кроме того, вы можете избежать малярной клавиши shared_from_this, сделав createA не членом (или статическим членом, если ему нужен доступ кB s рядов), принимая аргумент общего указателя:

A createA(std::shared_ptr<B> const & b) {return A(b);}

A complicatedCreateA()
{
    std::shared_ptr<B> b = std::make_shared<B>();
    return createA(b);
}
3 голосов
/ 23 января 2012

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

class B;

class A
{
public:
  std::unique_ptr<B> b;

  A(B b) : b(make_unique<B>(b)) { }
};

class B
{
public:
  std::unique_ptr<A> createA() { return make_unique<A>(*this); }
};

Тогда

std::unique_ptr<A> complicatedCreateA()
{
  B b; // or std::unique_ptr<B> bptr(make_unique<B>()); but here that's useless more work

  return b.createA();
}

И вам не нужно вызывать delete для возвращаемого значения createA или для члена A::b, потому что всякий раз, когда unique_ptr s выходит из области видимости, они очищают A* и B* они владеют.

Обратите внимание, что вы звоните make_unique<T>, чтобы сделать unique_ptr. Преимущество этого метода в том, что он более безопасен для исключений, чем простой new, хотя вы можете использовать и его, например, : b(new B(b)) и return std::unique_ptr<A>(new A(*this));.

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

1 голос
/ 23 января 2012

Это не простой случай, когда говорят «используйте умные указатели». B, кажется, является фабрикой A. У A есть указатель на фабричный объект, который его создал (и предположительно может создавать из него больше объектов A-типа).

Если вы хотите, чтобы B подсчитывался по ссылке, он должен быть "навязчиво" подсчитан по ссылке. Обычно это делается с помощью какого-то механизма addRef () / release (), и когда в последний раз вызывается release (), обычно выполняется удаление this

Я мог бы также предположить, что A в некотором роде полиморфен, так что вы можете получить объект для создания большего количества объектов его собственного типа (не обязательно клонов) через его абстрактную фабрику.

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

Что касается A, ваш B создаст новый с обычным новым, а затем вы положите его в shared_ptr или что-то еще.

Обратите внимание, что вы можете использовать ссылку здесь, например:

class A
{
 private:
    friend class B;

    explicit A( B const& b );

    B (const) & b_;

    A(const A&); // not implemented
 public:
    A* createNew() const;
    ~A();      
};

 A::A( B const& b )
     : b_( b )
 {
    b_.addRef(); // possibly, if reference counted
 }

 A::~A()
 {
    b_.release();
 }

 A* A::createNew() const
 {
    return b_.createA();
 }

для B

 A* B::createNew() const
 {
    return new A(*this); // addRef() will be invoked
 }

 void B::addRef() const
 {
    atomically_increment( &my_refCount );
 }

 void B::release() const
 {
    if( !atomically_decrement( &my_refCount ) )
        delete this;
 }

где B имеет изменяемый тип my_refCount, который подходит для атомного вызова увеличения / уменьшения для вашей системы.

Обратите внимание, что вы также можете использовать boost shared_ptr для B, но для этого потребуется извлечь B из boost::enable_shared_from_this<B>. Это также «навязчиво», но сделает большую работу за вас. Теперь вашему A нужно будет хранить boost::shared_ptr<B>, а B :: createA () будет вызывать shared_from_this().

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

0 голосов
/ 23 января 2012

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

A anotherMethod() {
    B b = new B();
    A a = b.createA();   //use a locally, or use with yet another object C, etc.           
    return b.createA();
} 

Нет необходимости создавать объекты с new B();, вы можете просто создать их в стеке, и время их жизни будет обрабатываться автоматически.

Просто сделайте это так:

A anotherMethod() {
    B b;
    A a = b.createA();   //use a locally, or use with yet another object C, etc.           
    return b.createA();
} 

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

0 голосов
/ 23 января 2012

Если у вас есть доступ к надстройке, вы можете использовать умные указатели. В зависимости от того, чего вы хотите достичь, вы можете использовать boost::shared_ptr (подсчет ссылок один) или boost::scoped_ptr.

Посмотрите на: http://www.boost.org/doc/libs/1_48_0/libs/smart_ptr/smart_ptr.htm

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