Пять подводных камней при использовании shared_ptr

Класс shared_ptr — это удобный инструмент, который может решить множество проблем разработчика. Однако для того, чтобы не совершать ошибок, необходимо отлично знать его устройство. Надеюсь, моя статья будет полезна тем, кто только начинает работать с этим инструментом.

Я расскажу о следующем:
  • что такое перекрестные ссылки;
  • чем опасны безымянные shared_ptr;
  • какие опасности подстерегают при использовании shared_ptr в многопоточной среде;
  • о чем важно помнить, создавая свою собственную освобождающую функцию для shared_ptr;
  • какие существуют особенности использования шаблона enable_shared_from_this.


Описанные проблемы имеют место как для boost::shared_ptr, так и для std::shared_ptr. В конце статьи вы найдете приложение с полными текстами программ, написанных для демонстрации описываемых особенностей (на примере библиотеки boost).

Перекрестные ссылки


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



При конструировании нового объекта создается объект со счетчиком и в него помещается значение 1. При копировании счетчик увеличивается на 1. При вызове деструктора (или при замене указателя путем присваивания, или вызова reset), счетчик уменьшается на 1.

Рассмотрим пример:
struct Widget {
    shared_ptr<Widget> otherWidget;
};

void foo() {
    shared_ptr<Widget> a(new Widget);
    shared_ptr<Widget> b(new Widget);
    a->otherWidget = b;
    // В этой точке у второго объекта счетчик ссылок = 2
    b->otherWidget = a;
    // В этой точке у обоих объектов счетчик ссылок = 2
}

Что произойдет при выходе объектов a и b из области определения? В деструкторе уменьшатся ссылки на объекты. У каждого объекта будет счетчик = 1 (ведь a все еще указывает на b, а b — на a). Объекты “держат” друг друга и у нашего приложения нет возможности получить к ним доступ — эти объекты “потеряны”.
Для решения этой проблемы существует weak_ptr. Одним из типичных случаев создания перекрестных ссылок является случай, когда один объект владеет коллекцией других объектов

struct RootWidget {
    list<shared_ptr<class Widget> > widgets;
};

struct Widget {
    shared_ptr<class RootWidget> parent;
};

При таком устройстве каждый Widget будет препятствовать удалению RootWidget и наоборот.



В таком случае нужно ответить на вопрос: “Кто кем владеет?”. Очевидно, что именно RootWidget в данном случае владеет объектами Widget, а не наоборот. Поэтому модифицировать пример нужно так:

struct Widget {
    weak_ptr<class RootWidget> parent;
};

Слабые ссылки не препятствуют удалению объекта. Они могут быть преобразованы в сильные двумя способами:

1) Конструктор shared_ptr
weak_ptr<Widget> w = …;
// В случае, если объект уже удален, в конструкторе shared_ptr будет сгенерировано исключение
shared_ptr<Widget> p( w );

2) Метод lock
weak_ptr<Widget> w = …;
// В случае, если объект уже удален, то p будет пустым указателем
if( shared_ptr<Widget> p = w.lock() ) {
// Объект не был удален – с ним можно работать
}

Вывод:
В случае возникновения в коде кольцевых ссылок, используйте weak_ptr для решения проблем.

Безымянные указатели


Проблема безымянных указателей относится к вопросу о “точках следования” (sequence points)
// shared_ptr, который передается в функцию foo - безымянный
foo( shared_ptr<Widget>(new Widget), bar() );

// shared_ptr, который передается в функцию foo имеет имя p
shared_ptr<Widget> p(new Widget);
foo( p, bar() );

Из этих двух вариантов документация рекомендует всегда использовать второй — давать указателям имена. Рассмотрим пример, когда функция bar определена вот так:
int bar() {
    throw std::runtime_error(“Exception from bar()”);
}

Дело в том, что в первом случае порядок конструирования не определен. Все зависит от конкретного компилятора и флагов компиляции. Например, это может произойти так:
  1. new Widget
  2. вызов функции bar
  3. конструирование shared_ptr
  4. вызов функции foo

Наверняка можно быть уверенным лишь в том, что вызов foo будет последним действием, а shared_ptr будет сконструирован после создания объекта (new Widget). Однако никаких гарантий того, что он будет сконструирован сразу после создания объекта, нет.

Если во время второго шага будет сгенерировано исключение (а оно в нашем примере сгенерировано будет), то Widget будет считаться сконструированным, но shared_ptr еще не будет им владеть. В итоге ссылка на этот объект будет потеряна. Я проверил данный пример на gcc 4.7.2. Порядок вызова был таким, что shared_ptr+new вне зависимости от опций компиляции не разделялись вызовом bar. Но полагаться именно на такое поведение не стоит – это не гарантировано. Буду признателен, если мне подскажут компилятор, его версию и опции компиляции, для которых подобный код приведет к ошибке.

Еще одна возможность для обхода проблемы анонимных shared_ptr — это использование функций make_shared или allocate_shared. Для нашего примера это будет выглядеть так:

foo( make_shared<Widget>(), bar() );

Данный пример выглядит даже более лаконично, чем исходный, а так же обладает рядом преимуществ в плане выделения памяти (вопросы эффективности оставим за пределами статьи). Допустим вызов make_shared с любым количество аргументов. Например следующий код вернет shared_ptr на строку, созданную через конструктор с одним параметром.

make_shared<string>("shared string");

Вывод:
Давайте shared_ptr имена, даже если код будет от этого менее лаконичным, либо воспользуйтесь для создания объектов
функциями make_shared и allocate_shared.

Проблема использования в разных потоках


Подсчет ссылок в shared_ptr построен с использованием атомарного счетчика. Мы без опаски используем указатели на один и тот же объект из разных потоков. Во всяком случае, мы не привыкли беспокоиться о подсчете ссылок (потокобезопасность самого объекта – другая проблема).

Допустим, у нас есть глобальный shared_ptr:
shared_ptr<Widget> globalSharedPtr(new Widget);

void read() {
    shared_ptr<Widget> x = globalSharedPtr;
    // Сделать что-нибудь с Widget
}

Запустите вызов read из разных потоков и вы увидите, что никаких проблем в коде не возникает (до тех пор, пока вы выполняете над Widget потокобезопасные для этого класса операции).

Допустим, есть еще одна функция:
void write() {
     globalSharedPtr.reset( new Widget );
}

Устройство shared_ptr достаточно сложно, поэтому я приведу код, который схематически поможет показать проблему. Разумеется, настоящий код выглядит иначе.
shared_ptr::shared_ptr(const shared_ptr<T>& x) {
A1:    pointer = x.pointer;
A2:    counter = x.counter;
A3:    atomic_increment( *counter );
}

shared_ptr<T>::reset(T* newObject) {
B1:    if( atomic_decrement( *counter ) == 0 ) {
B2:        delete pointer;
B3:        delete counter;
B4:    }
B5:    pointer = newObject;
B6:    counter = new Counter;
}

Допустим, первый поток начал копировать globalSharedPtr (read), а второй поток вызывает reset для этого же экземпляра указателя (write). В итоге может получиться следующее:
  1. Поток1 только что выполнил строку A2, но еще не перешел к строке A3 (атомарный инкремент).
  2. Поток2 в это время уменьшил счетчик на строке B1, увидел, что после уменьшения счетчик стал равен нулю и выполнил строки B2 и B3.
  3. Поток1 доходит до строки A3 и пытается атомарно увеличить счетчик, которого уже нет.

А может быть и так, что поток1 на строке A2 успеет увеличить счетчик до того, как поток2 вызовет удаление объектов, но после того как поток2 произвел уменьшение счетчика. Тогда мы получим новый shared_ptr, указывающий на удаленный счетчик и объект.

Можно написать подобный код:
shared_ptr<Widget> globalSharedPtr(new Widget);
mutex_t globalSharedPtrMutex;

void resetGlobal(Widget* x) {
    write_lock_t l(globalSharedPtrMutex);
    globalSharedPtr.reset( x );
}

shared_ptr<Widget> getGlobal() {
    read_lock_t l(globalSharedPtrMutex);
    return globalSharedPtr;
}

void read() {
    shared_ptr<Widget> x = getGlobal();
    // Вот с этим x теперь можно работать
}

void write() {
     resetGlobal( new Widget );
}

Теперь, используя такие функции, можно безопасно работать с этим shared_ptr.

Вывод: если какой-то экземпляр shared_ptr доступен разным потокам и может быть модифицирован, то необходимо позаботиться о синхронизации доступа к этому экземпляру shared_ptr.

Особенности времени разрушения освобождающего функтора для shared_ptr


Данная проблема может иметь место только в том случае, если вы используете собственный освобождающий функтор в сочетании со слабыми указателями (weak_ptr). Например, вы можете создать shared_ptr на основе другого shared_ptr, добавив новое действие перед удалением (по сути шаблон “Декоратор”). Так вы могли бы получить указатель для работы с базой данных, изъяв его из пула соединений, а по окончании работы клиента с указателем — вернуть его обратно в пул.
typedef shared_ptr<Connection> ptr_t;

class ConnectionReleaser {
    list<ptr_t>& whereToReturn;
    ptr_t connectionToRelease;
public:
    ConnectionReleaser(list<ptr_t>& lst, const ptr_t& x):whereToReturn(lst), connectionToRelease(x) {}

    void operator()(Connection*) {
        whereToReturn.push_back( connectionToRelease );
// Обратите внимание на следующую строчку
        connectionToRelease.reset();
    }
};

ptr_t getConnection() {
    ptr_t c( connectionList.back() );
    connectionList.pop_back();
    ptr_t r( c.get(), ConnectionReleaser( connectionList, c ) );
    return r; 
}



Проблема заключается в том, что объект, переданный в качестве освобождающего функтора для shared_ptr, будет разрушен только тогда, когда все ссылки на объект будут уничтожены — как сильные(shared_ptr), так и слабые(weak_ptr). Таким образом, если ConnectionReleaser не позаботится о том, чтобы “отпустить” переданный ему указатель (connectionToRelease), он будет держать сильную ссылку, пока существует хотя бы один weak_ptr от shared_ptr, созданного функцией getConnection. Это может привести к достаточно неприятному и неожиданному поведению вашего приложения.

Возможен так же вариант, когда вы воспользуетесь bind для создания освобождающего функтора. Например так:
void releaseConnection(std::list<ptr_t>& whereToReturn, ptr_t& connectionToRelease) {
    whereToReturn.push_back( connectionToRelease );
    // Обратите внимание на следующую строчку
    connectionToRelease.reset();
}

ptr_t getConnection() {
    ptr_t c( connectionList.back() );
    connectionList.pop_back();
    ptr_t r( c.get(), boost::bind(&releaseConnection, boost::ref(connectionList), c) );
    return r;
}


Помните, что bind копирует переданные ему аргументы (кроме случая с использованием boost::ref), и если среди них будет shared_ptr, то его тоже следует очистить, дабы избежать уже описанной проблемы.

Вывод: Выполните в освобождающей функции все действия, которые необходимо совершить при разрушении последней сильной ссылки. Сбросьте все shared_ptr, которые по какой-то причине являются членам вашего функтора. Если вы используете bind, то не забывайте, что он копирует переданные ему аргументы.

Особенности работы с шаблоном enable_shared_from_this


Иногда требуется получить shared_ptr из методов самого объекта. Попытка создания нового shared_ptr от this приведет к неопределенному поведению (скорее всего к аварийному завершению программы), в отличие от intrusive_ptr, для которого это является обычной практикой. Для решения этой проблемы был придуман шаблонный класс-примесь enable_shared_from_this.

Шаблон enable_shared_from_this устроен следующим образом: внутри класса содержится weak_ptr, в который при конструировании shared_ptr помещается ссылка на этот самый shared_ptr. При вызове метода shared_from_this объекта, weak_ptr преобразуется в shared_ptr через конструктор. Схематически шаблон выглядит так:
template<class T> 
class enable_shared_from_this {
    weak_ptr<T> weak_this_;
public:
    shared_ptr<T> shared_from_this() {
        // Преобразование слабой ссылки в сильную через конструктор shared_ptr
        shared_ptr<T> p( weak_this_ );
        return p;
    }
};

class Widget: public enable_shared_from_this<Widget> {};

Конструктор shared_ptr для этого случая схематически выглядит так:
shared_ptr::shared_ptr(T* object) {
    pointer = object;
    counter = new Counter;
    object->weak_this_ = *this;
}

Важно понимать, что при конструировании объекта weak_this_ еще ни на что не указывает. Правильная ссылка в нем появится только после того, как сконструированный объект будет передан в конструктор shared_ptr. Любая попытка вызова shared_from_this из конструктора приведет к bad_weak_ptr исключению.
struct BadWidget: public enable_shared_from_this<BadWidget> {
    BadWidget() {
        // При вызове shared_from_this() будет сгенерировано bad_weak_ptr
         cout << shared_from_this() << endl;
    }
};

К таким же последствиям приведет попытка обратиться к shared_from_this из деструктора, но уже по другой причине: в момент разрушения объекта уже считается, что на него не указывает никаких сильных ссылок (счетчик декрементирован).
struct BadWidget: public enable_shared_from_this<BadWidget> {
    ~BadWidget() {
        // При вызове shared_from_this() будет сгенерировано bad_weak_ptr
        cout << shared_from_this() << endl;
    }
};

Со вторым случаем (деструктор) мало что можно придумать. Единственный вариант — позаботиться о том, чтобы не вызывать shared_from_this и сделать так, чтобы этого не делали функции, которые вызывает деструктор.

С первым случаем все обстоит немного проще. Наверняка вы уже решили, что единственный способ для существования вашего объекта — shared_ptr, тогда будет уместно переместить конструктор объекта в закрытую часть класса и создать статический метод для создания shared_ptr нужного вам типа. Если при инициализации объекта вам нужно выполнить действия, которым потребуется shared_from_this, то для этой цели можно выделить логику в метод init.

class GoodWidget: public enable_shared_from_this<GoodWidget> {
        void init() {
                cout << shared_from_this() << endl;
        }
public:
        static shared_ptr<GoodWidget> create() {
                shared_ptr<GoodWidget> p(new GoodWidget);
                p->init();
                return p;
        }
};

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

Заключение


В статье рассмотрены 5 особенностей использования shared_ptr и представлены общие рекомендации по избежанию потенциальных проблем.

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

Литература




Приложение


В приложении представлены полные тексты программ для иллюстрации описанных в статье случаев

Демонстрация проблемы кольцевых ссылок
#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

class BadWidget {
	std::string name;
	boost::shared_ptr<BadWidget> otherWidget;
public:
	BadWidget(const std::string& n):name(n) {
		std::cout << "BadWidget " << name << std::endl;
	}

	~BadWidget() {
		std::cout << "~BadWidget " << name << std::endl;
	}

	void setOther(const boost::shared_ptr<BadWidget>& x) {
		otherWidget = x;
		std::cout << name << " now points to " << x->name << std::endl;
	}
};

class GoodWidget {
	std::string name;
	boost::weak_ptr<GoodWidget> otherWidget;
public:
	GoodWidget(const std::string& n):name(n) {
		std::cout << "GoodWidget " << name << std::endl;
	}

	~GoodWidget() {
		std::cout << "~GoodWidget " << name << std::endl;
	}

	void setOther(const boost::shared_ptr<GoodWidget>& x) {
		otherWidget = x;
		std::cout << name << " now points to " << x->name << std::endl;
	}
};

int main() {
	{ // В этом примере происходит утечка памяти
		std::cout << "====== Example 3" << std::endl;
		boost::shared_ptr<BadWidget> w1(new BadWidget("3_First"));
		boost::shared_ptr<BadWidget> w2(new BadWidget("3_Second"));
		w1->setOther( w2 );
		w2->setOther( w1 );
	}
	{ // А в этом примере использован weak_ptr и утечки памяти не происходит
		std::cout << "====== Example 3" << std::endl;
		boost::shared_ptr<GoodWidget> w1(new GoodWidget("4_First"));
		boost::shared_ptr<GoodWidget> w2(new GoodWidget("4_Second"));
		w1->setOther( w2 );
		w2->setOther( w1 );
	}
	return 0;
}

Демонстрация преобразования weak_ptr в shared_ptr
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

class Widget {};

int main() {
	boost::weak_ptr<Widget> w;
	// В этой точке weak_ptr ни на что не указывает
	// Метод lock вернет пустой указатель
	std::cout << __LINE__ << ": " << w.lock().get() << std::endl;
	// Конструирование shared_ptr от этого указателя приведет к исключению
	try {
		boost::shared_ptr<Widget> tmp ( w );
	} catch (const boost::bad_weak_ptr&) {
		std::cout << __LINE__ << ": bad_weak_ptr" << std::endl;
	}

	boost::shared_ptr<Widget> p(new Widget);
	// Теперь у weak_ptr есть значение
	w = p;

	// Метод lock вернет правильный указатель
	std::cout << __LINE__ << ": " << w.lock().get() << std::endl;
	// Конструирование shared_ptr от этого указателя тоже вернет правильный указатель. Исключения не будет
	std::cout << __LINE__ << ": " << boost::shared_ptr<Widget>( w ).get() << std::endl;

	// Сбросим указатель
	p.reset();
	// Сильных ссылок больше нет. У weak_ptr истек срок годности

	// Метод lock снова вернет пустой указатель
	std::cout << __LINE__ << ": " << w.lock().get() << std::endl;
	// Конструирование shared_ptr от этого указателя снова приведет к исключению
	try {
		boost::shared_ptr<Widget> tmp ( w );
	} catch (const boost::bad_weak_ptr&) {
		std::cout << __LINE__ << ": bad_weak_ptr" << std::endl;
	}
	return 0;
}

Демонстрация проблемы многопоточности для shared_ptr
#include <iostream>

#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>

typedef boost::shared_mutex mutex_t;
typedef boost::unique_lock<mutex_t> read_lock_t;
typedef boost::shared_lock<mutex_t> write_lock_t;

mutex_t globalMutex;
boost::shared_ptr<int> globalPtr(new int(0));

const int readThreads = 10;
const int maxOperations = 10000;

boost::shared_ptr<int> getPtr() {
// Закомментируйте следующую строку, чтобы ваше приложение упало
	read_lock_t l(globalMutex);
	return globalPtr;
}

void resetPtr(const boost::shared_ptr<int>& x) {
// Закомментируйте следующую строку, чтобы ваше приложение упало
	write_lock_t l(globalMutex);
	globalPtr = x;
}

void myRead() {
	for(int i = 0; i < maxOperations; ++i) {
    	boost::shared_ptr<int> p = getPtr();
	}
}

void myWrite() {
	for(int i = 0; i < maxOperations; ++i) {
		resetPtr( boost::shared_ptr<int>( new int(i)) );
	}
}

int main() {
	boost::thread_group tg;
	tg.create_thread( &myWrite );
	for(int i = 0; i < readThreads; ++i) {
		tg.create_thread( &myRead  );
	}
	tg.join_all();
	return 0;
}


Демонстрация проблемы deleter + weak_ptr
#include <string>
#include <list>
#include <iostream>
#include <stdexcept>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/bind.hpp>

class Connection {
	std::string name;
public:
	const std::string& getName() const { return name; }

	explicit Connection(const std::string& n):name(n) {
		std::cout << "Connection " << name << std::endl;
	}

	~Connection() {
		std::cout << "~Connection " << name << std::endl;
	}
};

typedef boost::shared_ptr<Connection> ptr_t;

class ConnectionPool {
	std::list<ptr_t> connections;

	// Этот класс предназначен для демонстрации первого варианта создания deleter (get1)
	class ConnectionReleaser {
		std::list<ptr_t>& whereToReturn;
		ptr_t connectionToRelease;
	public:
		ConnectionReleaser(std::list<ptr_t>& lst, const ptr_t& x):whereToReturn(lst), connectionToRelease(x) {}

		void operator()(Connection*) {
			whereToReturn.push_back( connectionToRelease );
			std::cout << "get1: Returned connection " << connectionToRelease->getName() << " to the list" << std::endl;

			// Закомментируйте след. строку и обратите внимание на разницу в выходной печати
			connectionToRelease.reset();
		}
	};

	// Эта функция предназначена для демонстрации второго варианта создания deleter (get2)
	static void releaseConnection(std::list<ptr_t>& whereToReturn, ptr_t& connectionToRelease) {
		whereToReturn.push_back( connectionToRelease );
		std::cout << "get2: Returned connection " << connectionToRelease->getName() << " to the list" << std::endl;

		// Закомментируйте следующую строку и обратите внимание на разницу в выходной печати
		connectionToRelease.reset();
	}

	ptr_t popConnection() {
		if( connections.empty() ) throw std::runtime_error("No connections left");
		ptr_t w( connections.back() );
		connections.pop_back();
		return w;
	}
public:
	ptr_t get1() {
		ptr_t w = popConnection();
		std::cout << "get1: Taken connection " << w->getName() << " from list" << std::endl;
		ptr_t r( w.get(), ConnectionReleaser( connections, w ) );
		return r;
	}

	ptr_t get2() {
		ptr_t w = popConnection();
		std::cout << "get2: Taken connection " << w->getName() << " from list" << std::endl;
		ptr_t r( w.get(), boost::bind(&releaseConnection, boost::ref(connections), w ));
		return r;
	}

	void add(const std::string& name) {
		connections.push_back( ptr_t(new Connection(name)) );
	}

	ConnectionPool() {
		std::cout << "ConnectionPool" << std::endl;
	}

	~ConnectionPool() {
		std::cout << "~ConnectionPool" << std::endl;
	}
};

int main() {
	boost::weak_ptr<Connection> weak1;
	boost::weak_ptr<Connection> weak2;
	{
		ConnectionPool cp;
		cp.add("One");
		cp.add("Two");

		ptr_t p1 = cp.get1();
		weak1 = p1;
		ptr_t p2 = cp.get2();
		weak2 = p2;
	}
	std::cout << "Here the ConnectionPool is out of scope, but weak_ptrs are not" << std::endl;
	return 0;
}

Демонстрация проблемы с enable_shared_from_this
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

class BadWidget1: public boost::enable_shared_from_this<BadWidget1> {
public:
	BadWidget1() {
		std::cout << "Constructor" << std::endl;
		std::cout << shared_from_this() << std::endl;
	}
};

class BadWidget2: public boost::enable_shared_from_this<BadWidget2> {
public:
	~BadWidget2() {
		std::cout << "Destructor" << std::endl;
		std::cout << shared_from_this() << std::endl;
	}
};

class GoodWidget: public boost::enable_shared_from_this<GoodWidget> {
	GoodWidget() {}

	void init() {
		std::cout << "init()" << std::endl;
		std::cout << shared_from_this() << std::endl;
	}
public:
	static boost::shared_ptr<GoodWidget> create() {
		boost::shared_ptr<GoodWidget> p(new GoodWidget);
		p->init();
		return p;
	}
};


int main() {
	boost::shared_ptr<GoodWidget> good = GoodWidget::create();
	try {
		boost::shared_ptr<BadWidget1> bad1(new BadWidget1);
	} catch( const boost::bad_weak_ptr&) {
		std::cout << "Caught bad_weak_ptr for BadWidget1" << std::endl;
	}
	try {
		boost::shared_ptr<BadWidget2> bad2(new BadWidget2);
		// При компиляции с новым стандартом вы получите terminate
		// т.к. считается, что из деструктора по умолчанию не может быть сгенерировано исключение
	} catch( const boost::bad_weak_ptr&) {
		std::cout << "Caught bad_weak_ptr for BadWidget2" << std::endl;
	}
	return 0;
}
Поделиться публикацией

Комментарии 53

    +19
    Проблему с безымянными указателями можно решить с помощью std::make_shared
      +1
      Кстати ценное замечание, спасибо. Что-то я об этом забыл упомянуть.
        0
        Не можно, а нужно. Вообще, везде, где есть возможность, стоит использовать std::make_shared. Странно что в статье про это нет ни слова.
          0
          Внес исправления
          +1
          Главная проблема тут в том, что пользоваться weak_ptr практически никогда нельзя. В любой мало-мальски сложной программе, в которой нельзя однозначно построить дерево владения объектами друг друга, тем более в многопоточной, weak_ptr может протухнуть в любой, самый неожиданный момент. Поэтому, прежде чем работать с объектом по weak_ptr, обязательно нужно сначала создать временный shared_ptr (и не забыть убить его в конце работы). Определить конец работы легко, если последовательность операций представляет собой дерево. А вот на графе (асинхронно-многопоточный код) это в общем случае невозможно.
          То есть, ещё раз, для графа shared/weak указатели не работают. Не работает сам алгоритм подсчета ссылок. А для случая дерева они не нужны, там достаточно обычных динамических или даже локальных переменных.
          Процитирую свой же, заминусованный комментарий.
          Weak_ptr? Они что, шутят? С третьей, или какой там по счету, попытки сделать в STL автоматическое управление памятью, предлагается weak_ptr? Годика через три вопрос «приведите пример, когда нужен weak_ptr» будет встречаться на собеседованиях с такой же частотой, как и вопрос про виртуальное базовое наследование. И с такой же частотой употребляться на практике. Потому что это еще один, тысячный способ гарантированно прострелить себе ногу. А без weak_ptr не будет нормально работать shared_ptr. Ну и зачем тогда всё это?
            +3
            Посмотрите на Objective-C — язык полностью построенный на модели подсчета ссылок. И нет никаких проблем (на самом деле есть, но не те, что описаны вами) с многопоточностью. И там weak ссылки работают отлично, во многих местах. Там все просто — создаете strong ссылку из weak для скоупа, в котором работаете. Дя этого shared_ptr / weak_ptr и разрабатывались.
              +1
              Это всё здорово, когда есть строго очерченный скоуп. Т.е. дерево управления.
              Во многих программах — сетевых демонах, сервисах, гуёвых приложениях дерева нет. Есть граф. Вот создался/загрузился объект. Потом пришло событие, потом другое событие к тому же объекту, потом третье. Обработчики событий могут порождать длительные асинхронные обращения к той же базе данных, например, или к worker threads. Порядок и даже сам факт прихода этих событий вам неизвестен. Будете использовать weak_ptr — получите тухлые указатели. Будете использовать shared — получите out_of_memory.
              Ну наверное не зря во всяких java и php для управления памятью используют алгоритмы, основанные на анализе достижимости вершин графа.
                +1
                Obj-C используется как раз в гуёвых приложениях, и проблем в графом объектов (именно, тех, которые описали вы) не наблюдается.
                Будете использовать weak_ptr — получите тухлые указатели. Будете использовать shared — получите out_of_memory.
                Использование только сильных/слабых ссылок вместо использования голых указателей дает возможность уйти от мыслей о работе с памятью к мыслям о органицации графа зависимостей.
                Предположим, что объект A имеет слабую ссылку на B. В методе A из слабой ссылки (weak_ptr) создается сильная ссылка(shared_ptr), и внутри скоупа этого метода идет работа с сильной ссылкой — она точно не протухнет, пока shared_ptr жив. Эту ссылку можно передавать в другие методы, даже асинхронные, и объект A будет жить пока это необходимо этому асинхронному методу (например).
                Системе подчета ссылок не одно десятилетие (Cocoa/Objective-C, Delphy), и у нее есть свои недостатки, но я очень рад, что она наконец-то появилась и в C++11.
                  +2
                  Так.
                  Давайте смотреть на проблему в целом.
                  Проблема такая: есть ссылающиеся друг на друга объекты, которые образуют циклический граф, причём этот граф динамически изменяется в процессе выполнения программы. Необходимо своевременно удалять те объекты, у которых не осталось путей к некоторому объекту-корню.

                  В случае с weak/shared указателями для решения этой задачи предлагается использовать алгоритм подсчета ссылок. Но так как этот алгоритм работает только с деревьями и не работает с циклическими графами, предлагается обязать программиста строить на этом графе остовное дерево из shared-указателей и объявлять все не попавшие в это дерево связи как weak указатели. И не только строить, но и всегда поддерживать это дерево в корректном состоянии.
                  Цена ошибок в поддержании корректности дерева варьируется от очень неприятных (неудаляемая память в случае появления лишних shared_ptr, образующих цикл) до фатальных (работа с протухшим weak_ptr портит память, что может всплыть неизвестно где, а потеря одного shared_ptr близко к корню вызывает каскад деструкторов по всему графу). В цикломатически сложных многопоточных программах подобные ошибки могут возникать только при определенных фазах луны при сатурне в третьем доме и относятся к категории не повторяемых и совершенно не отлаживаемых. Алгоритм, провоцирующий фатальные, неповторяемые, не отлаживаемые ошибки сложно назвать хорошим, не правда ли?
                  Ситуация ухудшается тем, что не существует какого-либо единственного способа построить остовное дерево на произвольном графе. Это значит, что программист должен постоянно держать в уме, где у него shared-рёбра, а где weak, и не допускать совершенно никаких ошибок. А что, если программистов много? А что, если часть библиотек идёт без исходников, и неизвестно, что у них там внутри? Волна деструкторов ведь может прийти и из сторонней либы, опять же при сатурне в третьем доме.
                  И всё из-за того, что мы изначально используем для графа алгоритм, предназначенный только для дерева.

                  Окай, во всяких java используются алгоритмы непосредственно для графов, основанные на выявлении компонентов связности. Но так как граф динамически изменяется, тут тоже есть свои подводные камни, а именно:
                  * фризы (замораживание всех потоков) на время работы сборщика мусора
                  * перерасход памяти в ситуации, когда объекты уже удалены, но сборщик еще не запустился
                  * немасштабируемость на большие графы (Завалишин писал, что фриз для всей операционной системы фантом занимает несколько часов).
                  Поэтому появились сложные многопроходные алгоритмы, не требующие или почти не требующие фризов, зато не гарантирующие какого-либо определенного максимального времени жизни объектов после объявления их ненужными.

                  Теперь внимание, правильный ответ для большинства случаев.
                  В большинстве случаев у нас есть точка, когда уже совершенно точно можно объявлять объекты подлежащими удалению. Запрос полностью обработан, юзер закрыл документ, геймер прошел уровень, клиент закрыл соединение и пр.
                  Вместо плясок с графом мы складываем все связанные с запросом объекты в отдельный пул временных объектов. Затем, после точки Х, мы просто удаляем сразу все объекты из этого пула, не разбираясь с их внутренними взаимосвязями.
                  Всё.
                  Единственное ограничение — все эти объекты должны влезать в память одновременно. Лечится введением подзапросов со своими собственными пулами.
                    +2
                    Давайте-же смотреть на проблему в целом.

                    Давайте, представим для простоты, что граф наших объектов — это дерево, с жесткими ссылками (и, слабыми/weak обратными ссылками). Варианты, когда листья дерева могут ссылаться на другие листья пока не рассматриваем.
                    shared(strong) ссылки реализуют семантику владения без необходимости вызова деструктора самостоятельно.

                    Основная проблема с голыми указателями — это определить эту самую семантику владения — кто должен вызывать delete/delete[]? shared_ptr/weak_ptr как раз указывают явно эту семантику в коде.

                    Вернемся к нашему примеру. У нас есть «дерево» объектов. Мы решаем, что нам нужно сделать что-то в другом потоке в каким-то поддеревом. Мы передаем в другой поток это поддерево, скопировав shared_ptr. Тем самым мы разделили владение этим поддеревом с другой сущностью (потоком). Теперь, предположим наступает тот самый момент,
                    когда уже совершенно точно можно объявлять объекты подлежащими удалению
                    Мы отказываемся от владения вершиной дерева. Если никто не завладел вершиной дополнительно, то дерево будет удалено, кроме поддерева, которым совладеет другой поток. Это поддерево останется валидным, все объекты живы пока им владеет другая сущность. Теперь поток что-то сделал, как-то преобразовал это поддерево и решил оповестить о этом «надкорень» этого дерева. он попытается получить сильную ссылку из слабой и узнает, что объекта уже нет. Все. Он может остановится и не делать ничего. Поток не упадет только лишь потому, что кто-то где-то решил, что час Ч настал и можно удалять всё дерево.

                    Заметтьте, в описаном примере никто не вызывает деструктор. Это не нужно. Нужно всего лишь отказаться от владения. Программируя я не хочу думать когда и кто должен будет вызывать конструкторы/деструкторы (в идеале). Я хочу описывать задачу на более высоком уровне. Я хочу сказать «на момент вызова этого метода» (или «пока жив этот объект») такие-то данные должны оставаться валидными. И это все. Когда мне данные перестают быть нужны я хочу просто отказаться от владения ими. Эту семантику владения и описывают strong/weak умные указатели.

                    Описанный вами пулл — это лишь одно из возможных решений. И не всегда выгодное. Например, если данные, с которыми мы работаем иммутабельны, то shared_ptr будет сильно выигрывать по памяти, поскольку не потребуется их копировать в каждый из N пуллов, будут созданы только N shared_ptr, указывающие на одну область памяти.
                      +1
                      1. Ну, и? Где продолжение-то? Вы предположили, что граф ваших объектов это дерево, для простоты. Хорошо, в простом случае shared/weak работают. Я даже согласен признать, что синтаксически они слаще, чем ручной вызов new/delete (хотя для дерева и они тоже работают довольно очевидным образом, нужна только аккуратность).
                      Теперь допустим, что в версии 2.0 в вашу программу добавили еще один тип объектов, и дерево перестало быть деревом, а стало нормальным циклическим графом, и никаким очевидным образом к дереву теперь не сводится. И?

                      2. Пул объектов не является серебряной пулей, главный его недостаток — неуниверсальность. Именно поэтому он не используется в managed-языках, где с программиста хотели снять вообще все заботы о памяти. Но в плюсах, где программист может выбирать калибр для стрельбы в свою ногу, было бы логичным ввести эту технику на уровне языковых конструкций, а не на уровне STL (чтобы ей штатно пользовались все либы, а не так, что здесь std::shared, там boost::shared, а там mybicycle::shared).
                        +1
                        Давайте пример.
                        Классическая триада Модель (объект) — Представление (гуй) — Операция (некая длительная обработка модели, с превью текущих результатов в гуе).
                        Между всеми тремя отношения многие-ко-многим (в одном представлении или операции может использоваться несколько разных объектов, к одному объекту может быть прицеплено несколько гуев и т.п.). В том числе допустимы ситуации с нулём (не ко всем объектам/операциям прицеплен гуй).
                        Гуй и операции, разумеется, выполняются в разных потоках.
                        Ну попробуйте разрулить это при помощи shared/weak.
                          0
                          Связи в направлении «представления — операции — модели» — сильные. Связи в обратном направлении — слабые.

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

                          Представлениями исходно владеет оконная библиотека. Также любые объекты удерживаются при помощи локальных переменных пока исполняются. Что я забыл?..
                    +1
                    Граф есть хаос, дерево рождает порядок.
                    Зачастую лучше представить граф в виде набора деревьев. Так программы получаются надежнее.
                +4
                «Перекрестные ссылки» и «кольцевые ссылки» имеют вполне устоявшийся в русском языке термин «Циклические ссылки» (от англ. retain cycle).
                Описанный в статье простой и очевидный вид циклических ссылок. Гораздо более часто встречающийся в реальных приложениях, и гораздо более трудно находимые — это когда объект A имеет жесткую ссылку на B, который имеет жесткую ссылку на С, который имеет жесткую ссылку на A.
                  +1
                  Спасибо за комментарий. Абсолютно согласен, что в реальном коде циклические ссылки встречаются в гораздо более «извращенных формах». Боюсь, что упоминание о более сложных случаях, серьезно усложнило бы материал и увеличило бы объем.
                  –3
                  Честно говоря, пункт «Проблема использования в разных потоках», где read и write, выглядит как явный косяк реализации shared_ptr. Имхо, эту проблему должны были решить разработчики shared_ptr, потому что заставлять юзеров вешать мьютекс на shared_ptr, внутри которого тоже мьютекс — это изврат какой-то. Вообще, слабо верится. Обещали же вроде многопоточность в документации. Надо будет проверить.
                    +3
                    Речь в статье про один и тот же экземпляр shared_ptr. Если shared_ptr скопировать, то внутри одного потока его можно свободно использовать и копировать дальше. Атомарный счетчик именно этой цели и служит. Реализация с мутексом была бы очень тяжелой по производительности.
                      0
                      Я тогда не очень понимаю смысл «многопоточности» shared_ptr, если его всё равно надо закрывать мьютексом. Приведите, пожалуйста, пример, что можно делать с shared_ptr из нескольких потоков без дополнительного мьютекса.
                        +3
                        Безопасность shared_ptr заключается в безопасности манипуляций над отдельными экземплярами shared_ptr, пусть даже имеющими ссылку на один и тот же блок подсчёта ссылок(который и защищён атомарностью). Т.е. пользователя не должна заботить деталь реализации блока подсчёта ссылок. Он работает так, как-будто никакого разделяемого блока нет и в помине. По моему мнению, автор привёл немного неудачный пример т.к. эта проблема присуща любому разделяемому ресурсу и shared_ptr выделять смысла нет никакого.
                      0
                      многопоточность нужна для только для эффективности, эффективность и встроенная защита от многопоточности это взаимоисключаюшие параграфы. Поэтому разработчики абсолютно правы, что в эффективый класс не стали добавлять блокировок
                      +2
                      1. Имхо сначала бы не мешало почитать доку, никто вам и не обещал, что shared_ptr будет разрешать циклические зависимости (перекрестные ссылки).
                      2. ну про безымянные уже всё сказано, и для этого создавалось семейство make_xxx функций.
                      3. опять следовало бы почитать для начала документацию, shared_ptr потокобезопасен тогда, когда объект уже захвачен, reset же это захват нового объекта и она не является потокобезопасной функцией.

                      Остальные механизмы не использовал, но исходя из того, что по первым 3м почитать доку было лень, боюсь предположить.
                        +2
                        Зря Вы так, помимо отсутствия упоминания make_shared содержание заметки выглядит вполне релевантной заголовку. Автор привел примеры подводных камней и пояснил как их обойти. Ещё я бы переписал часть про многопоточность, т.к. не совсем в тему она тут.
                          0
                          Спасибо за комментарий! Это мой первый опыт в таких публикациях. Я решил нужным упомянуть об этой проблеме, т.к. в действительности не все программисты знают в каких случаях атомарный счетчик уберегает от проблем. Я думаю, что мог выразить мысль не совсем внятно. А как именно вы предлагаете изменить часть про многопоточность?
                            0
                            Я бы заострил внимание, что является потокобезопасным в shared_ptr, а факт про reset оставить как дополнение; мол, shared_ptr ничем не отличается от других разделяемых ресурсов и имеет такие-то проблемы. Просто, как мне кажется. не раскрыта тема потокобезопасности shared_ptr. Что же в нём потокобезопасного? Вы справедливо отметили, что в нём не безопасно, но не достаточно уделили внимания безопасной части, по моему мнению.
                              0
                              То есть Вы хотите сказать, что выглядит так, будто постулируется, что все операции для всех экземпляров, указывающих на объект, должны выполняться под локом?

                              Завтра утром внесу правки, если придумаю. Боюсь разбухания текста. Сокращал, а получилось все равно очень много.
                                +2
                                Нет, просто тема потокобезопасности блока подсчёта ссылок упоминается лишь вскользь, зато весь параграф посвящён тому, что справедливо для любого объекта не обладающего потокобезопасными методами. Поэтому мне и не ясно, зачем выделять shared_ptr отдельно. А вот если написать подробнее про потокобезопасность блока ссылок и после этого показать, что это не делает сам shared_ptr потокобезопасным, то это будет полная картина. Я не думаю, что там стоит много писать. Просто добавить пару предложений, чтобы было понятно, что безопасно, а что нет в sahred_ptr
                          0
                          Это публикация ориентирована на тех кто начал (начинает) работать с данной технологией (как описано во вступлении). Сомневаюсь, что для Гуру она будет полезна. Я не претендую на совершенный разбор «неизведанных глубин».
                          Вы справедливо заметили, вся эта информация есть в документации и частями в различных публикациях. Я лишь хочу облегчить путь для тех, кто им еще не ходил, чтобы сэкономить им часы отладки. Ведь не все люди никогда не совершают ошибок.

                          В любом случае, спасибо за Ваше мнение, ведь не получить хотя бы одного негативного отклика было бы подозрительно.
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            А можно вот это предложение немного детализировать?
                            «Иногда требуется получить shared_ptr из методов самого объекта. Попытка создания нового shared_ptr от this приведет к неопределенному поведению (скорее всего к аварийному завершению программы), в отличие от intrusive_ptr, для которого это является обычной практикой.»
                            1) Почему это приводит к неопределенному поведению? 2) В двух словах о intrusive_ptr.
                              0
                              1) Потому что тогда на один объект будет заведено два счетчика
                              Эквивалентно
                              Widget* p = new Widget;
                              shared_ptr<Widget> a( p );
                              shared_ptr<Widget> b( p );
                              

                              При выходе второго объекта из области определения будет попытка освобождения уже освобожденного объекта

                              2) intrusive_ptr содержит встроенный счетчик и поэтому такая конструкция
                              Widget* p = new Widget;
                              intrusive_ptr<Widget> a( p );
                              intrusive_ptr<Widget> b( p );
                              

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

                              Поэтому для intrusive_ptr валидной является конструкция
                              intrusive_ptr<Widget>( this );
                              
                              –1
                              Что-то про безымянные указатели странно.
                              foo(bar(new object1()), new object2());
                              new это фактический вызов обычной функции поэтому «new object1()» и вызов bar не могут быть разделены — это нарушение порядка выполнения операций (см. порядок выполнения операций).
                                +1
                                Лишь отсылаю к документации
                                www.boost.org/doc/libs/1_54_0/libs/smart_ptr/shared_ptr.htm
                                начиная с «Best Practices».
                                Там есть ссылка на Сартара

                                Порядок выполнения действительно не может быть нарушен в том плане, что
                                1) bar будет вычислен раньше foo
                                2) new object1 будет вычислен раньше bar
                                3) new object 2 будет вычислен раньше foo
                                В остальном как повезет.
                                  +1
                                  То, что аргументы функции могут вычисляться в любом порядке — это ладно. Можно, наверное, даже придумать какие-то оптимизации в этом плане. Но я вообще не представляю, что должно твориться в голове у разработчика компилятора, чтобы начать вычисление первого аргумента функции, выполнить его частично, отложить в сторону, выполнить вычисление второго аргумента функции и затем вернуться к окончанию вычисления первого. Эта схема бъёт по всему — по производительности CPU, по памяти, по попаданиям данных в кеш — кто в здравом уме это сделает в компиляторе?
                                    +2
                                    Хотел ответить однозначно на вопрос, а не выходит, потому как вопрос некорректный (но при этом правильный и вполне логичный). Попробую показать на простом примере:

                                    1. В коде:
                                      func(arg1, arg2)
                                    2. В псевдо-ассемблере на входе оптимизатора:
                                      mov arg1, r5   // arg1
                                      mov r1, [addr] // arg2
                                      mov arg2, [r1] // arg2
                                      call func
                                      
                                    3. В псевдо-ассемблере на выходе оптимизатора:
                                      mov r1, [addr] // arg2
                                      mov arg1, r5   // arg1
                                      mov arg2, [r1] // arg2
                                      call func
                                      

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

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

                                    Может будет проще представить вызов функции как последовательное присвоение значений N переменным, а затем сам переход, чем он по сути и является. В этом случае видно, что в вызове нет ничего особенного: компилятор перемешивает инструкции из разных выражений всегда, если это не влияет на результат, и тут происходит то же самое.

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

                                    foo( shared_ptr<Widget>(new Widget), bar() );


                                    подвыражение shared_ptr(new Widget) даёт точку следования, и компилятор (пмсм) не может вставить вызов bar() между вызовами new Widget и shared_ptr().
                                      0

                                      Эта точка следования относится только к вызову new Widget и вызову конструктора shared_ptr<Widget>(...). На вычисление bar() эта точка следования никак не влияет, и bar() может вычисляться когда компилятору угодно.

                                        0
                                        Думаю всё же, что влияет. Логика моя такова: компилятор может вычислять аргументы foo в любом порядке, это так. Но это не значит, что он может в любом порядке вычислять любые подвыражения выражений для этих аргументов. Либо уж он сначала вычисляет bar(), либо сначала вычисляет целиком shared_ptr(new Widget), целиком именно потому, что вызов конструктора shared_ptr есть точка следования. Могу ошибаться, конечно, просто хотелось бы увидеть конкретный wording из стандарта, например.
                                          0
                                          Нет, скорее всего, я тут неправ.
                                    0
                                    У вас в разделе «Особенности времени разрушения освобождающего функтора для shared_ptr» первый пример работает, даже если закомментировать строку
                                    // Обратите внимание на следующую строчку
                                    connectionToRelease.reset();
                                    

                                    Вот демонстрация: ideone.com/kR7OBv
                                    И я что-то не могу придумать, как ещё надо исправить пример, чтобы ConnectionReleaser не был уничтожен.
                                      0
                                      Немного изменил свой пример (изначально в нём не было weak_ptr, так как не полностью понял описываемую проблему), но теперь ConnectionReleaser не уничтожается не зависимо от того, есть или нет указанная выше строка, то есть эта строка не влияет на вывод.
                                        +1
                                        Извиняюсь за флуд в комментариях, но я наконец-то разобрался что проблема в том, что если закомментировать указанную строку, то соединение из пула будет продолжать жить, до тех пор, пока не будут уничтожены все weak_ptr, хотя к этому времени пул уже может не существовать. Подводные камни в том, что может ожидаться, что соединения уничтожаются при уничтожении пула, но здесь это оказывается не так.
                                          +1
                                          Абсолютно верно. Вероятнее всего в случае, если reset вызван не будет, ничего страшного не произойдет, но если это вдруг окажется важным для корректного функционирования, то потенциально это может стоить часов отладки.
                                            0
                                            Кстати полный листинг моего примера есть в приложении. Для этого и некоторых друх случаев
                                              0
                                              Да, его я видел и тоже запускал: ideone.com/yRpnJJ (под C++11, без Boost)

                                              Кстати, в разделе про enable_shared_from_this тоже не сразу понятны все проблемы.

                                              Во-первых, не ясно в чём могут быть проблемы, если не использовать enable_shared_from_this. А проблемы в том, что можно создать два share_ptr, один где-то во вне, а другой возвращённый методом объекта. Потом будет попытка дважды освободить одну и ту же область памяти.

                                              Во-вторых, про enable_shared_from_this не прописано явно, что до вызова shared_from_this() обязательно должен быть создан shared_ptr (хотя по коду это видно), если этого не сделать, то weak_ptr не инициализируется. Метод init() как раз решает эту проблему, создавая shared_ptr перед вызовом shared_from_this().

                                              В пример можно добавить такой код:
                                              class BadWidget3: public boost::enable_shared_from_this<BadWidget1> {
                                              public:
                                                  BadWidget3() {
                                                  }
                                              
                                                 shared_ptr<BadWidget3> f() {
                                                      return shared_from_this();
                                                 }
                                              };
                                              
                                              main()
                                              {
                                                  BadWidget3* w = new BadWidget3;
                                                  shared_ptr<BadWidget3> w1 = w->f();
                                              }
                                              
                                          +1
                                          Безымянные указатели

                                          Неплохо бы было сразу указать ссылку на источник этого знания. Авторитетный кстати. До Саттера как-то нигде не было и намёка на возможность подобной коллизии.

                                          Проблема использования в разных потоках

                                          Строго говоря это не является проблемой shared_ptr. Это проблема потоконебезопасного использования объекта, тип объекта вторичен.
                                          Но если очень хочется то можно ( из буста ):
                                          template<class T> void atomic_store( shared_ptr<T> * p, shared_ptr<T> r )
                                          {
                                              boost::detail::spinlock_pool<2>::scoped_lock lock( p );
                                              p->swap( r );
                                          }
                                          

                                          P.S. Именно этот пост побудил меня зарегистрироваться на Хабре, для этого комментария.
                                            0
                                            Такой вопрос:
                                            А если shared_ptr Ptr( new MyClass[10] );?

                                            Произойдёт ли корректное освобождение памяти и вызовы всех 10 деструкторов ~MyClass при выходе Ptr из области видимости?
                                            Сам отвечу на первый вопрос — корректное освобождение памяти произойдёт по любому (даже если вызвать delete 'указатель на массив' без квадратных скобок происходит освобождение памяти под массив, а вот вызовы всех деструкторов — не факт), а что с shared_ptr — вызовет ли все деструкторы?
                                              0
                                              В вашем случае shared_ptr «не знает» размер массива, поэтому он никак не сможет вызвать деструкторы.
                                              При создании shared_ptr можно указать свой deliter либо в C++11 есть контейнер std::array. Он здесь подошел бы идеально.
                                              Не уверен, но, вроде бы, только unique_ptr умеет вызывать delete[] для массивов. shared_ptr тоже?
                                                0
                                                Не знаю.
                                                А еще вопрос чисто по плюсам (без пользовательских библиотек, входящих в стандарт)
                                                Что будет если:

                                                MyClass *my = new MyClass();
                                                delete [] my;

                                                ?
                                                То есть удаляю как массив, создаю как одиночный.
                                                  0
                                                  Undefined behavior, естественно, если пользоваться стандартным аллокатором.

                                                  3.7.4.2/3:
                                                  <...> and the behavior is undefined if the value supplied to operator delete[](void*) in the standard library is not one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
                                                    0
                                                    Ясно. Я ошибся, сказав «без пользовательских библиотек, входящих в стандарт» ибо оператор new имеет свою имплементацию в стандартной библиотеке(обычно обертка над malloc с вызовами конструкторов и бросанием исключений). — в принципе ничем не отличается от, скажем, vector. Правда есть еще ключевое слово new — не помню чем отличается(по моему как раз и вызывает конструктор).
                                                    А такой вопрос: а почему в стандарте по C++ описано, как должны себя вести пользовательские компоненты, стандартная library? ведь пользовательские компоненты — это не сам язык C++. Пользовательские компоненты написаны на C++ и они конечно имеют свой стандарт поведения — но не являются самим языком. По идее должно быть два источника инфы: pure C++ и C++ standart library, stl, partialy boost(что из буста перешло в C++11), etc
                                                      0
                                                      Ясно. Я ошибся, сказав «без пользовательских библиотек, входящих в стандарт» ибо оператор new имеет свою имплементацию в стандартной библиотеке(обычно обертка над malloc с вызовами конструкторов и бросанием исключений). — в принципе ничем не отличается от, скажем, vector. Правда есть еще ключевое слово new — не помню чем отличается(по моему как раз и вызывает конструктор).
                                                      Это ключевое слово как раз и вызывает operator new, который должен выделить память под объект, а потом вызывает необходимый конструктор.

                                                      А такой вопрос: а почему в стандарте по C++ описано, как должны себя вести пользовательские компоненты, стандартная library? ведь пользовательские компоненты — это не сам язык C++. Пользовательские компоненты написаны на C++ и они конечно имеют свой стандарт поведения — но не являются самим языком. По идее должно быть два источника инфы: pure C++ и C++ standart library, stl, partialy boost(что из буста перешло в C++11), etc
                                                      Ну, библиотека потому и стандартная, потому что входит в стандарт. Это обязательная библиотека, которая должна быть в любой реализации. Хоть разделение на ядро и библиотеку в общем-то здравое, но я не знаю таких языков, где стандартная библиотека разрабатывается совершенно отдельно от ядра языка.
                                                      0
                                                      я нашел про делеты и ньюшки со скобками и без. Есть две стратегии и в обоих компилятор так компилирует, что размер массива запоминается. Первый подход — с переаллоцированием для дополнительной переменной где хранится размер, второй подход — отдельный ассоциантивный массив с указателями в кач-ве ключа и размером в кач-ве значения. Эти механизмы реализуются на уровне компиляторов. www.parashift.com/c++-faq-lite/num-elems-in-new-array.html
                                                      поэтому компилятор знает сколько деструкторов вызывать.
                                                      Поэтому неопределенное поведение при использовании неправильного delete

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

                                              Самое читаемое