Я активно использовал интеллектуальные указатели (boost:: shared_ptr, если быть точным) в моих проектах за последние два года. Я понимаю и ценю их преимущества, и я вообще их очень люблю. Но чем больше я их использую, тем больше я скучаю по детерминированному поведению С++ относительно управления памятью и RAII, которые мне кажутся на языке программирования. Умные указатели упрощают процесс управления памятью и обеспечивают автоматическую сборку мусора между прочим, но проблема заключается в том, что использование автоматической сборки мусора вообще и умного указателя конкретно вводит некоторую степень неопределенности в порядке (де) инициализации. Этот индетерминизм отбирает контроль у программистов и, как я понял в последнее время, делает работу по разработке и разработке API-интерфейсов, использование которых не известно заранее на момент разработки, изнурительно трудоемкое, потому что все шаблоны использования и угловые случаи должны быть хорошо поняты.
Чтобы уточнить, в настоящее время я разрабатываю API. Части этого API требуют, чтобы определенные объекты были инициализированы раньше или уничтожены после других объектов. Иными словами, порядок (де) инициализации важен в разы. Чтобы дать вам простой пример, скажем, у нас есть класс под названием "Система". Система предоставляет некоторые базовые функции (вход в наш пример) и содержит ряд подсистем через интеллектуальные указатели.
class System {
public:
boost::shared_ptr< Subsystem > GetSubsystem( unsigned int index ) {
assert( index < mSubsystems.size() );
return mSubsystems[ index ];
}
void LogMessage( const std::string& message ) {
std::cout << message << std::endl;
}
private:
typedef std::vector< boost::shared_ptr< Subsystem > > SubsystemList;
SubsystemList mSubsystems;
};
class Subsystem {
public:
Subsystem( System* pParentSystem )
: mpParentSystem( pParentSystem ) {
}
~Subsystem() {
pParentSubsystem->LogMessage( "Destroying..." );
// Destroy this subsystem: deallocate memory, release resource, etc.
}
/*
Other stuff here
*/
private:
System * pParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
};
Как вы уже можете сказать, подсистема имеет смысл только в контексте системы. Но подсистема в такой конструкции может легко пережить свою родительскую систему.
int main() {
{
boost::shared_ptr< Subsystem > pSomeSubsystem;
{
boost::shared_ptr< System > pSystem( new System );
pSomeSubsystem = pSystem->GetSubsystem( /* some index */ );
} // Our System would go out of scope and be destroyed here, but the Subsystem that pSomeSubsystem points to will not be destroyed.
} // pSomeSubsystem would go out of scope here but wait a second, how are we going to log messages in Subsystem destructor?! Its parent System is destroyed after all. BOOM!
return 0;
}
Если бы мы использовали исходные указатели для хранения подсистем, мы бы уничтожили подсистемы, когда наша система опустилась, конечно же, pSomeSubsystem будет висящим указателем.
Хотя разработчик API не должен защищать программистов-клиентов от самих себя, неплохо было бы сделать API удобным для использования правильно и трудно использовать неправильно. Поэтому я спрашиваю вас, ребята. Как вы думаете? Как мне решить эту проблему? Как бы вы создали такую систему?
Спасибо заранее, Джош