[updated][c++] this + shared_ptr как?

elenangel

Как использовать this внутри класса, если снаружи этот класс принадлежит кому-то по shared_ptr, но не известно кому?
Использовать вместе raw pointer и shared нехорошо. Завернуть где надо shared_ptr<ThisClass>(this) тоже нельзя - его тогда 2 раза грохнут.
Придумал пока в классе хранить weak_ptr на него самого, но его надо задавать после конструктора когда уже известен указатель и сохранен снаружи в какой-то shared.
То есть придется делать что-то вроде

class Test
{
public:
Test(){}
void setWeakThis(weak_ptr<Test> weakThis_p)
{
weakThis = weakThis_p;
}

void doSomething()
{
shared_ptr<Test> tmp = weakThis.lock();
if(!tmp)
{
throw BlahBlahException();
}
shared_ptr<OtherClass> p = make_shared<p>(tmp)
}

private:
weak_ptr<Test> weakThis;
};
...

shared_ptr<Test> p = make_shared<Test>();
weak_ptr<Test> wp(p);
p->setWeakPtr(wp);



и так для каждого класса и для каждого оператора new.
можно завернуть в макрос или затемплейтить, конечно, но нет ли способа лучше?

solambo

А enable_shared_from_this из буста не то?

apl13

и так для каждого класса и для каждого оператора new.
А перегрузить оператор new?

apl13

Ну или как вариант специализировать shared_ptr<Test>.

ava3443

унаследуйся от enable_shared_from_this<T>

elenangel

а мне не дают буст. и c++11 тоже не дают.
фактически у меня только std::tr1::shared_ptr есть, ну и weak

elenangel

похоже это как раз то, но так как я без буста, попробую покрасть идею.

Maurog

ты на каком компиляторе? если есть shared_ptr, то должен быть и enable_shared_from_this

elenangel

gcc 4.4
Using built-in specs.
Target: i686-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.4-14ubuntu5.1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
Thread model: posix
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5.1)

elenangel

о, да! алилуйя, оно там есть!

Maurog

gcc 4.4
загляни в исходники, неужели там нет того, что надо?
вот тут пишут, что в 4.4.1 уже есть: http://stackoverflow.com/questions/2571850/why-does-enable-s...

Maurog

о, да!
поздравляю, Шарик, ты балбес (с) :grin:

elenangel

и снова здравствуйте.
делаю вот так, и это работает:
#include <iostream>
#include <tr1/memory>

class Root;

class Child
{
public:
Child(std::tr1::shared_ptr<Root> owner_p) : owner(owner_p)
{
}
~Child()
{
std::cout << "Child::~Child()" << std::endl;
}
void test();
private:
std::tr1::shared_ptr<Root> owner;
};

class Root : public std::tr1::enable_shared_from_this<Root>
{
public:
Root()
{

}
void init()
{
child = std::tr1::shared_ptr<Child>(new Child(shared_from_this()));
}

void say()
{
std::cout << "I'm a root" << std::flush;
}

void test()
{
child->test();
}

~Root()
{
std::cout << "Root::~Root()" << std::endl;
}
private:
std::tr1::shared_ptr<Child> child;
};

void Child::test()
{
std::cout << "I'm a child, my owner says: ";
owner->say();
std::cout << std::endl;
}

int main(int , char *[])
{
std::tr1::shared_ptr<Root> r(new Root());
r->init();
r->test();
return 0;
}

а когда делаю так, как на мой взгляд удобнее и естественнее, то получаю terminate called after throwing an instance of 'std::tr1::bad_weak_ptr'
what(): tr1::bad_weak_ptr
#include <iostream>
#include <tr1/memory>

class Root;

class Child
{
public:
Child(std::tr1::shared_ptr<Root> owner_p) : owner(owner_p)
{
}
~Child()
{
std::cout << "Child::~Child()" << std::endl;
}
void test();
private:
std::tr1::shared_ptr<Root> owner;
};

class Root : public std::tr1::enable_shared_from_this<Root>
{
public:
Root()
{
child = std::tr1::shared_ptr<Child>(new Child(shared_from_this()));
}

void say()
{
std::cout << "I'm a root" << std::flush;
}

void test()
{
child->test();
}

~Root()
{
std::cout << "Root::~Root()" << std::endl;
}
private:
std::tr1::shared_ptr<Child> child;
};

void Child::test()
{
std::cout << "I'm a child, my owner says: ";
owner->say();
std::cout << std::endl;
}

int main(int , char *[])
{
std::tr1::shared_ptr<Root> r(new Root());
r->test();
return 0;
}

elenangel

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

okunek

Шаред фром зыс в кишках использует вик, который инициализируется при первом создании шаред птр-а с объектом, унаследованным от енабле шаред фром зыс. При первом вызове конструктора у тебя никаких шаредов еще нету. В бусте это выглядит так:
 
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn( p ) // Y must be complete
{
boost::detail::sp_enable_shared_from_this( this, p, p );
}

 

template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe )
{
if( pe != 0 )
{
pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
}
}


template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
{
if( weak_this_.expired() )
{
weak_this_ = shared_ptr<T>( *ppx, py );
}
}

 

shared_ptr<T> shared_from_this()
{
shared_ptr<T> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}

elenangel

сделал вот так, выглядит как хак, но работает. и 2 раза зовет деструктор Root
 
#include <iostream>
#include <tr1/memory>

class Root;

class Child
{
public:
Child(std::tr1::shared_ptr<Root> owner_p) : owner(owner_p)
{
}
~Child()
{
std::cout << "Child::~Child()" << std::endl;
}
void test();
private:
std::tr1::shared_ptr<Root> owner;
};

class Root : public std::tr1::enable_shared_from_this<Root>
{
public:
Root()
{
std::tr1::shared_ptr<Root> sharedThis(this);
child = std::tr1::shared_ptr<Child>(new Child(sharedThis));
}

void say()
{
std::cout << "I'm a root" << std::flush;
}

void test()
{
child->test();
}

~Root()
{
std::cout << "Root::~Root()" << std::endl;
}
private:
std::tr1::shared_ptr<Child> child;
};

void Child::test()
{
std::cout << "I'm a child, my owner says: ";
owner->say();
std::cout << std::endl;
}

int main(int , char *[])
{
std::tr1::shared_ptr<Root> r(new Root());
r->test();
return 0;
}

okunek

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

Maurog

сделал вот так
эти смарт пойнтеры для тебя слишком смарт :grin:

Maurog

работает
1) у тебя проблема с дизайне: циклические зависимости между сущностями. постарайся переработать
2) если с переработкой туго, то надо четко понимать время жизни каждого объекта и кто кем владеет
в частности: если A ссылается на B, а B ссылается на A, то лишь одна из этих ссылок должна быть сильной (shared_ptr), а вторую надо сделать слабой (weak_ptr), иначе будут утечки памяти
3) проработай последовательность создания и уничтожения сущностей
один из возможных и логичных сценариев (метакод):

RootPtr root = CreateRoot();
ChildPtr child = CreateChild();
root->AddChild(child);

//implementation
void Root::AddChild(ChildPtr child)
{
m_child = child;
child->SetRoot(enable_shared_from_this());
}

void Child::SetRoot(RootPtr root)
{
//store week pointer on root
m_RootWeek = root;
}

успехов

elenangel

У меня сейчас все построено на обычных raw поинтерах, в основном root владеет child'ом, который может в свою очередь содержать вложенные объекты, удаляется это дерево в основном от корня, иногда из середины кто-то сообщает родителю что пора удаляться, тот то же самое своему родителю, а тот, кто обнаруживает, что он корень - удаляет child'ов и завершается сам. Есть отдельные утечки, в основном связанные с объектами-делегатами (непонятно, делегат который у меня в руке - это временный объект-обёртка с указателем на чей-то this и указателем на метод или самостоятельный объект, который удалять нельзя, который реализует нужный интерфейс и потому передан в качестве обработчика) и отдельные утечки с объектами-эвентами, их создают в одном треде, кладут в хрен знает какую очередь, которую предоставили снаружи, а в другом неизвестном месте их достают из очереди, используют и удаляют, иногда получался косяк если эвент передается из треда в тред или клонируется по пути.
 m_child = child;
child->SetRoot(enable_shared_from_this());

вот именно этого я хотел избежать - было очень удобно, когда все что можно создавалось в конструкторе. Очень не хотелось вводить что-то вроде virtual afterConstruction().
Про weak я так понял у меня ссылка вверх на owner должна быть weak.
Вообще думалось ввести некий smart_pointer который является корнем наследования для 2 или более поинтеров, которые соответственно внутри содержат либо weak либо shared, оба ведут себя как указатели, а в деструкторе делают соответствующее вложенному указателю действие. Чтобы можно было внутрь объекта отдавать любой смарт поинтер по полиморфизму (по ссылки или по указателю) и он мог им пользоваться независимо от его "сильности", а решать какой именно поинтер я отдам я мог каждый раз в месте передачи, а не жестко кодировать в сигнатуре функции через которую передаю. Однако это выглядит слишком сложно и пугает возможностью запутать самого себя (и коллегу, который будет это использовать) и получить вдобавок оверхед на порядок больше чем при работе даже с std::tr1::shared_ptr.

elenangel

я догадываюсь. я думал внутренний weak выцепит кишки из первого использованного шареда и назначит их второму при присваивании. оказалось что это не так.

elenangel

придумал не шибко красивое решение, которое работает без функции init(), фактически дополнительная работа, которую нужно сделать после создания экземпляра класса вынесена в оператор приведения типа,
эстетику портит звёздочка, которая появилась перед new.
 
#include <iostream>
#include <tr1/memory>

class Root;

class Child
{
public:
Child(std::tr1::weak_ptr<Root> owner_p) : owner(owner_p)
{
}
~Child()
{
std::cout << "Child::~Child()" << std::endl;
}
void test();
private:
std::tr1::weak_ptr<Root> owner;
};

class Root : public std::tr1::enable_shared_from_this<Root>
{
public:
Root()
{
std::tr1::shared_ptr<Root> sharedThis(this);
child = std::tr1::shared_ptr<Child>(new Child(sharedThis));
tmpThis = sharedThis;
}

void say()
{
std::cout << "I'm a root" << std::flush;
}

operator std::tr1::shared_ptr<Root>()
{
std::tr1::shared_ptr<Root> result = shared_from_this();
tmpThis.reset();
return result;
}

void test()
{
child->test();
}

~Root()
{
std::cout << "Root::~Root()" << std::endl;
}
private:
std::tr1::shared_ptr<Child> child;
std::tr1::shared_ptr<Root> tmpThis;
};

void Child::test()
{
std::cout << "I'm a child, my owner says: ";
owner.lock()->say();
std::cout << std::endl;
}

int main(int , char *[])
{
std::tr1::shared_ptr<Root> r( * new Root());
r->test();
return 0;
}

Maurog

придумал
Оставить комментарий
Имя или ник:
Комментарий: