Проблемы с shared_ptr при возврате обновленной версии из функции-члена - PullRequest
3 голосов
/ 28 марта 2011

Я недавно пробовал shared_ptr и наткнулся на немного странный случай. То, что я хочу, это функция-член шаблона, которая способна возвращать shared_ptr своего производного типа. Я использую Visual Studio 2010, у которой есть доступ к некоторым новым стандартам c ++ 0x, но я предполагаю, что boost shared_ptr ведет себя аналогично.

Это работало нормально с голыми указателями. Я только что вернул dynamic_cast (это). Однако я немного озадачен тем, что даже при использовании enable_shared_from_this объект пытается удалить себя после вызова функции (что плохо). Я, вероятно, подхожу к этому неправильно, но я хотел бы решить, как смоделировать эквивалент пустого указателя (который работал) из следующего (это код, с которым у меня проблемы).

//assume we have a virtual function as well.
class BaseClass : public std::enable_shared_from_this<BaseClass>
{
  ....
  template<typename DerivedClass>
  std::shared_ptr<DerivedClass> BaseClass::getThis(){
     //I had some assert code here to ensure typeid matched
     return std::dynamic_pointer_cast<DerivedClass>(shared_from_this());
  }
}

edit : Кажется, функция работает правильно, проблема была в том, как я ее использовал. Это плохо, например, сделать следующее:

std::shared_ptr<DerivedClass> p = std::make_shared<DerivedClass>();
p->getType<DerivedClass>->someOtherFunctionOnlyInTheDerivedClass();

Это не проблема:

std::shared_ptr<BaseClass> p = std::make_shared<DerivedClass>();
p->getType<DerivedClass>->someOtherFunctionOnlyInTheDerivedClass();

Я не совсем уверен, если это проблема с преобразованием в тот же тип, или проблема подсчета ссылок. В любом случае, я делал что-то глупое, и это ломалось, избегая ненужного вызова getType в этот момент, кажется, работает нормально во всех остальных случаях, когда я его использую. Может быть, кто-то может точно объяснить, что приводит к разрыву первого примера с работающим вторым примером. Я назначу очки этому ответу.

Ответы [ 2 ]

5 голосов
/ 28 марта 2011

Чтобы расширить ответ Стюарта и (возможно) объяснить, почему у вас произошел сбой, лучше всего предположить, что вы вызывали getType для экземпляра stack-alloc'd. Это большая ошибка при использовании enable_shared_from_this.

#include <memory>

class Base : public std::enable_shared_from_this<Base>
{
public:
    virtual ~Base() {}

    template <typename D>
    std::shared_ptr<D> getType()
    {
        return std::dynamic_pointer_cast<D>(shared_from_this());
    }
};

class Derived : public Base
{
public:
    void f() {}
};

int main()
{
    std::shared_ptr<Derived> d = std::make_shared<Derived>();
    d->getType<Derived>()->f(); // fine

    Derived d2;
    Base* p = &d2;
    p->getType<Derived()>->f(); // will attempt to delete d2 after f() returns.

    return 0;
}

Причина, по которой это происходит, заключается в том, что счетчик ссылок d2 равен нулю, когда он находится в стеке. Вызов shared_from_this возвращает shared_ptr, который увеличивает счетчик ссылок до единицы. Как только этот указатель выходит из области видимости, он уменьшает его счетчик до нуля, который затем пытается удалить экземпляр, который, конечно, находится в стеке.

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

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

Во всяком случае, у меня нет проблемы, которую вы описываете с помощью следующего минимально скомпилируемого примера (который выглядит как код, который вы описали выше):

#include <memory>

class Base : public std::enable_shared_from_this<Base>
{
public:
    virtual ~Base() {}

    template <typename D>
    std::shared_ptr<D> getType()
    {
        return std::dynamic_pointer_cast<D>(shared_from_this());
    }
};

class Derived : public Base
{
public:
    void f() {}
};

int main()
{
    std::shared_ptr<Derived> d = std::make_shared<Derived>();
    d->getType<Derived>()->f();
    return 0;
}

Это сбой для вас?

...