Comments 26
Информация полезная (чтобы не городить костыли и подпорки) и интересная.
Единственное — у вас опечатка в заголовке.
Я сначала даже подумал, что это действительно какой-то секретный новый share_ptr :)
Единственное — у вас опечатка в заголовке.
Я сначала даже подумал, что это действительно какой-то секретный новый share_ptr :)
+3
del
-1
И позволю себе процитировать слова Stephan T. Lavavej:
Этот конструктор настолько секретный, что даже сопровождающие STL не знают о нём...
:)
Спасибо за перевод.
This constructor is so secret, not even STL maintainers know about it...
Этот конструктор настолько секретный, что даже сопровождающие STL не знают о нём...
:)
Спасибо за перевод.
+4
Возможно, у меня маловато опыта использования shared_ptr, но мне кажется что пример в разделе «Совместное использование подобъектов» выглядит немного странно. Если в библиотеку нужно передавать поле, на которое организована ссылка через shared_ptr и при этом раньше этого поля «умирает» объект, в котором оно содержится — с логикой приложения что-то категорически не так и нужно фиксить это, а не использовать сомнительные костыли.
И ещё вопрос — насколько нормальной практикой является оборачивание поля объекта в shared_ptr в стиле того, как это в том же примере сделано? Сам несколько раз порывался делать так, но не решился — показалось, что как-то это неправильно.
И ещё вопрос — насколько нормальной практикой является оборачивание поля объекта в shared_ptr в стиле того, как это в том же примере сделано? Сам несколько раз порывался делать так, но не решился — показалось, что как-то это неправильно.
+6
" с логикой приложения что-то категорически не так и нужно фиксить это, а не использовать сомнительные костыли." —
В реальной жизни «левая» логика часто может быть:
1. В сторонней библиотеке, не доступной для изменений.
2. В нашем коде, который по сотне причин не доступен для изменений. Например: время изменения (долгое), цена изменения (большая), сертификация/утверждение измененной версии, левая нога начальника, с которой он встал сегодня, и т.д.
В реальной жизни «левая» логика часто может быть:
1. В сторонней библиотеке, не доступной для изменений.
2. В нашем коде, который по сотне причин не доступен для изменений. Например: время изменения (долгое), цена изменения (большая), сертификация/утверждение измененной версии, левая нога начальника, с которой он встал сегодня, и т.д.
+2
Поддержу semenyakinVS
В примере «Совместное использование объектов» есть 2 варианта (и вообще — их всего 2) когда функция store_for_later(std::shared_ptr) использует экземпляр типа Y после «смерти» экземпляра типа X:
1) асинхронный вызов.
2) сохранение в другой глобальной структуре, к которой могут обращаться иные функции.
Если мы говорим про первый вариант, то данное использование вообще не верно, т.к. объект явно разделяемый, а объектов синхронизации нет => архитектурная ошибка.
Если мы говорим про второй вариант, а в статье судя по названию функции он как раз и имелся в виду, то мы САМИ ОБЯЗАНЫ использовать сию функцию с особой осторожностью и САМИ ОБЕСПЕЧИТЬ время жизни объекта типа X достаточное для работы всех библиотечных функций, которые зависят от поля типа Y. Иначе у нас ситуация один в один с «висячей ссылкой», а это => архитектурная ошибка.
В примере «Совместное использование объектов» есть 2 варианта (и вообще — их всего 2) когда функция store_for_later(std::shared_ptr) использует экземпляр типа Y после «смерти» экземпляра типа X:
1) асинхронный вызов.
2) сохранение в другой глобальной структуре, к которой могут обращаться иные функции.
Если мы говорим про первый вариант, то данное использование вообще не верно, т.к. объект явно разделяемый, а объектов синхронизации нет => архитектурная ошибка.
Если мы говорим про второй вариант, а в статье судя по названию функции он как раз и имелся в виду, то мы САМИ ОБЯЗАНЫ использовать сию функцию с особой осторожностью и САМИ ОБЕСПЕЧИТЬ время жизни объекта типа X достаточное для работы всех библиотечных функций, которые зависят от поля типа Y. Иначе у нас ситуация один в один с «висячей ссылкой», а это => архитектурная ошибка.
+1
Если мы говорим про первый вариант, то данное использование вообще не верно, т.к. объект явно разделяемый, а объектов синхронизации нет => архитектурная ошибка.Почему же? Если первый поток забывает про объект сразу после совершения асинхронного вызова — то объект не является разделяемым между потоками, он между ними передается.
Кстати, кто вообще сказал, что нет объектов синхронизации? Нам никто не мешает использовать этот подход и синхронизацию одновременно. Просто статья не про синхронизацию была.
… Иначе у нас ситуация один в один с «висячей ссылкой», а это => архитектурная ошибка.Почему висячая ссылка является архитектурной ошибкой, если она «висит» правильно?
PS вообще говоря, данный конструктор нужен в первую очередь для таких функций как std::static_pointer_cast и std::dynamic_pointer_cast. Но когда мощности наследования не хватает — имеет смысл использовать агрегацию — и тут этот конструктор понадобится.
+2
1. Я вовсе не спорил с semenyakinVS, а лишь дополнил его, сказав, что не всегда грязное белье можно постирать. Поэтому…
2. Не вижу, как хоть что-то из вышеописанного опровергает мои отнюдь не архитектурные аргументы.
2. Не вижу, как хоть что-то из вышеописанного опровергает мои отнюдь не архитектурные аргументы.
0
Пример —
внешний объект — Dom document, например xml.
внутри него — узлы, хранящие weak_ptr на документ и позволяющие его получить.
Тогда можно передать клиенту узел для обработки, а не пару «документ+узел».
Так документ будет жить за счёт жизни узла, а получить документ можно будет, вызвав у узла метод GetDocument(), создающий shared-ссылку из weak.
Другой пример: человек, у него есть руки-ноги. Держишь человека за руку, логично ожидать, что он не убежит (не удалится)
В COM
внешний объект — Dom document, например xml.
внутри него — узлы, хранящие weak_ptr на документ и позволяющие его получить.
Тогда можно передать клиенту узел для обработки, а не пару «документ+узел».
Так документ будет жить за счёт жизни узла, а получить документ можно будет, вызвав у узла метод GetDocument(), создающий shared-ссылку из weak.
Другой пример: человек, у него есть руки-ноги. Держишь человека за руку, логично ожидать, что он не убежит (не удалится)
В COM
+4
Не дописал. В com передается для похожих целей pOuterUnknown для целей агрегации
+1
Кажется, понял. И, если я всё правильно понял, я, кажется, сталкивался с такой проблемой во время работы над системой отображения иерархии объектов (иерахрические view в mvc, как в Cocoa да и, наверно, почти во всех GUI-архитектурах). Я решил вопрос поставив следующее условие: родительская вьюшка должна жить до тех пор, пока имеется хоть одна ссылка на неё либо на вложенные в неё вьюшки. В вашем примере — если есть хотя бы одна ссылка на руку/ногу или на тело (если нет ссылки на ногу, например, но есть на тело — нога удаляется).
По поводу GetDocument() — а это безопасно «увеличивать» степень владения объектом? Я всегда писал свой код исходя из того, что делать shared-ссылку из weak-ссылки архитектурно некорректно: мы вводим объект в «сильное» владение из контекста, в котором подразумевалось «слабое» владение… Или в некоторых случаях это всё-таки допустимо?
По поводу GetDocument() — а это безопасно «увеличивать» степень владения объектом? Я всегда писал свой код исходя из того, что делать shared-ссылку из weak-ссылки архитектурно некорректно: мы вводим объект в «сильное» владение из контекста, в котором подразумевалось «слабое» владение… Или в некоторых случаях это всё-таки допустимо?
0
Разумеется, это допустимо! Ведь без этой операции слабые ссылки и вовсе теряют смысл :)
Конкретно же в этом случае можно отказаться даже от слабых ссылок и хранить просто указатель — ведь дерево не исчезнет пока жива хоть одна вершина.
Конкретно же в этом случае можно отказаться даже от слабых ссылок и хранить просто указатель — ведь дерево не исчезнет пока жива хоть одна вершина.
0
Разумеется, это допустимо! Ведь без этой операции слабые ссылки и вовсе теряют смысл :)
Ага… Вот как. Буду знать. Я думал что они используются для получения защищённого «слабого» доступа к объекту — то есть от raw-указателя отличаются тем, что позволяют выполнять проверку на то, есть ли ещё объект по ссылке.
Конкретно же в этом случае можно отказаться даже от слабых ссылок и хранить просто указатель — ведь дерево не исчезнет пока жива хоть одна вершина.
Не совсем, там API именно на умных указателях построено.
В таком вот стиле
П.С.: Как-то кривовато код подсвечивается… Вроде С++ поставил ему, а всё равно криво как-то.
StrongRef<View> theView = window()->rootView();
StrongRef<View> theSubview = theView.createSubview();
StrongRef<View> theSubSubview = theSubview.createSubview(); // Иерархия: theView <- theSubview <- theSubSubview.
// Освобождаем theSubview. Вьюшка, на которую ссылалась эта ссылка не удаляется -
// на неё есть ещё один StrongRef во вьюшке, на которую ссылается theSubSubview.
theSubview = StrongRef<View>::Null();
// А вот теперь удаляется и та вьюшка, на которую theSubview ссылалась, и та, которая на которую theSubSubview -
// мы убрали ссылку на вьюшку, которая ссылается на парент-вьюшку.
theSubSubview = StrongRef<View>::Null();
П.С.: Как-то кривовато код подсвечивается… Вроде С++ поставил ему, а всё равно криво как-то.
0
Ага… Вот как. Буду знать. Я думал что они используются для получения защищённого «слабого» доступа к объекту — то есть от raw-указателя отличаются тем, что позволяют выполнять проверку на то, есть ли ещё объект по ссылке.А как вы, собственно, можете получить доступ к объекту, на который указывает weak_ptr, кроме как вызвав метод lock, который возвращает shared_ptr?
0
М-да, внимательнее почитаю о стандартных умных указателях… Если честно, я сразу свои писал. Да, стыдно.
0
Если ваш слабый указатель ведет себя по-другому, проверьте архитектуру своей программы. Представьте такую ситуацию:
Вроде бы все нормально? А теперь представим, что метод bar() косвенно очищает p1 где-то внутри. Что получилось? А получилось, что пока метод объекта исполнялся, сам объект удалили. Упс!
Именно поэтому в STL нельзя разыменовать слабый указатель иначе как создав временный сильный.
StrongRef<Foo> p1 = new Foo();
WeakRef<Foo> p2 = p1;
if (p2) p2->bar(); // Или любой другой способ разыменования слабого указателя с предварительной проверкой
Вроде бы все нормально? А теперь представим, что метод bar() косвенно очищает p1 где-то внутри. Что получилось? А получилось, что пока метод объекта исполнялся, сам объект удалили. Упс!
Именно поэтому в STL нельзя разыменовать слабый указатель иначе как создав временный сильный.
0
А теперь представим, что метод bar() косвенно очищает p1 где-то внутри.
А не будет ли такое поведение само по себе архитектурно ошибочным?
0
Нет. Ведь это может быть слушатель сетевого интерфейса, который вызывает обработчик сетевых запросов, который обращается к маршрутизатору сетевых запросов, который передает запрос службе управления, которая разбирает запрос и обнаруживает команду на перезапуск сервера.
Умные указатели должны работать независимо от того, насколько запутана архитектура программы.
Умные указатели должны работать независимо от того, насколько запутана архитектура программы.
0
Добавлю, что weak_ptr может еще неожиданно обнулиться в многопоточном приложении, когда в нашем потоке есть только слабая ссылка, а сильные ссылки удалились в фоновом потоке.
Необходимое получение сильного указателя гарантирует, что объект будет жив, пока мы с ним работаем.
Ну и вот так пользоваться weak ptr в многопоточной среде небезопасно:
Вот годная статья «Пять подводных камней при использование shared_ptr»
habrahabr.ru/post/191018
Необходимое получение сильного указателя гарантирует, что объект будет жив, пока мы с ним работаем.
Ну и вот так пользоваться weak ptr в многопоточной среде небезопасно:
weak_ptr<Obj> w;
...
if (w.lock())
{
// до повторного вызова lock счетчик ссылок может обнулиться в другом потоке,
// объект разрушиться.
// В дебаге мы словим assert, а в релизе - неопределенное поведение,
// причем это будет проявляться нестабильно
w.lock()->DoSomething()
}
Вот годная статья «Пять подводных камней при использование shared_ptr»
habrahabr.ru/post/191018
0
Набросал proof of concept (документ с рекурсивным добавлением глав), иллюстрирующий пользу от данного конструктора, а также от enable_shared_from_this:
документ не удаляется, пока есть ссылки хотя бы на одну из его глав. Т.к. каждая глава хранит weak_ptr на документ и обычный указатель на parent, всегда есть возможность получить как сам документ, так и ходить вверх по родительским главам.
Оно же на гитхаб
документ не удаляется, пока есть ссылки хотя бы на одну из его глав. Т.к. каждая глава хранит weak_ptr на документ и обычный указатель на parent, всегда есть возможность получить как сам документ, так и ходить вверх по родительским главам.
Проверено на VS 2013
#include <memory>
#include <vector>
#include <set>
#include <stdexcept>
#include <algorithm>
#include <cassert>
#include <string>
#include <iostream>
#include <functional>
using namespace std;
struct Document;
typedef weak_ptr<Document> DocumentWeakPtr;
typedef shared_ptr<Document> DocumentSharedPtr;
typedef shared_ptr<const Document> ConstDocumentSharedPtr;
struct Chapter;
typedef weak_ptr<Chapter> ChapterWeakPtr;
typedef shared_ptr<Chapter> ChapterSharedPtr;
typedef shared_ptr<const Chapter> ConstChapterSharedPtr;
// Интерфейс контейнера глав
struct IChapterContainer
{
virtual size_t GetChapterCount()const throw() = 0;
virtual ChapterSharedPtr GetChapter(size_t index) = 0;
virtual ConstChapterSharedPtr GetChapter(size_t index)const = 0;
virtual const ChapterSharedPtr & AddChapter(const ChapterSharedPtr& chapter) = 0;
virtual const ChapterSharedPtr & RemoveChapter(const ChapterSharedPtr& chapter) = 0;
virtual DocumentSharedPtr GetDocument() throw () = 0;
virtual ConstDocumentSharedPtr GetDocument()const throw() = 0;
protected:
~IChapterContainer(){}
};
// Реализация контейнера глав
template <typename ThisType>
struct ChapterContainer
: IChapterContainer
, enable_shared_from_this<ThisType>
{
// Добавляет главу в контейнер (строгая гарантия безопасности исключений)
const ChapterSharedPtr & AddChapter(const ChapterSharedPtr& chapter) override
{
// Главу нельзя добавить, если она находится в каком-либо контейнере
if (chapter->m_parent)
{
throw logic_error("The chapter is already added to somewhere");
}
// Запрещаем добавлять главы, относящиеся к чужому документу
if (chapter->m_document.lock() != GetDocument())
{
throw logic_error("The chapter belongs to a different document");
}
m_chapters.push_back(chapter);
chapter->m_parent = this;
return chapter;
}
// Удаляет главу из контейнера (строгая гарантия безопасности исключений)
const ChapterSharedPtr & RemoveChapter(const ChapterSharedPtr& chapter)
{
// Нельзя удалить чужую главу
if (chapter->m_parent != this)
{
throw invalid_argument("The chapter does not belong to this chapter container");
}
auto pos = find_if(m_chapters.begin(), m_chapters.end(), [&](const ChapterWeakPtr& item){
return item.lock() == chapter;
});
assert(pos != m_chapters.end());
m_chapters.erase(pos);
chapter->m_parent = nullptr;
return chapter;
}
virtual size_t GetChapterCount() const throw() override
{
return m_chapters.size();
}
virtual ChapterSharedPtr GetChapter(size_t index) override
{
return m_chapters.at(index).lock();
}
virtual ConstChapterSharedPtr GetChapter(size_t index) const override
{
return m_chapters.at(index).lock();
}
private:
vector<ChapterWeakPtr> m_chapters;
};
// Глава. Хранит заглавие, ссылки на документ-владелец и на родительский контейнер
// Глава также может хранить внутри себя дочерние главы
struct Chapter
: ChapterContainer<Chapter>
{
friend struct Document;
friend struct ChapterContainer < Chapter > ;
friend struct ChapterContainer < Document >;
virtual ConstDocumentSharedPtr GetDocument()const throw() override
{
return m_document.lock();
}
virtual DocumentSharedPtr GetDocument() throw() override
{
return m_document.lock();
}
// Возвращаем ссылку на родительский контейнер
shared_ptr<const IChapterContainer> GetParent()const throw()
{
return {GetDocument(), m_parent};
}
// Возвращаем ссылку на родительский контейнер
shared_ptr<IChapterContainer> GetParent() throw()
{
return{ GetDocument(), m_parent };
}
string GetTitle()const
{
return m_title;
}
~Chapter()
{
cout << "The chapter" << m_title << " has been destroyed" << endl;
}
Chapter(const Chapter&) = delete;
Chapter& operator=(const Chapter&) = delete;
private:
Chapter(const DocumentSharedPtr & document, const string& title = string())
: m_document(document)
, m_title(title)
{
}
DocumentWeakPtr m_document;
string m_title;
IChapterContainer* m_parent = nullptr;
};
// Документ. Управляет главами
struct Document
: ChapterContainer<Document>
{
Document(const Document&) = delete;
Document& operator=(const Document&) = delete;
static DocumentSharedPtr Create()
{
// Нельзя использовать make_shared из-за приватного деструктора
return DocumentSharedPtr(new Document());
}
virtual DocumentSharedPtr GetDocument() throw () override
{
return shared_from_this();
}
virtual ConstDocumentSharedPtr GetDocument() const throw() override
{
return shared_from_this();
}
// Создает главу и сохраняет ее внутри документа. Обеспечивает строгую гарантию безопасности исключений
shared_ptr<Chapter> CreateChapter(const string& title = string())
{
// Получаем сильную ссылку на самих себя
auto strongThis = shared_from_this();
// Создаем главу, которая сохранит слабую ссылку на нас
// make_unique не доступен из-за приватного конструктора, поэтому создаем через new
unique_ptr<Chapter> chapter(new Chapter(strongThis, title)); // Может выбросить исключение, но не страшно
// Помещаем ее во множество глав документа
auto result = m_chapters.insert(move(chapter)); // Может выбросить исключение, но не страшно
// Оборачиваем указатель указатель в shared_ptr
return shared_ptr<Chapter>(strongThis, result.first->get()); // не выбрасывает исключений
}
~Document()
{
cout << "The document is being destroyed" << endl;
}
private:
Document() = default;
set<unique_ptr<Chapter>> m_chapters;
};
void PrintTableOfContents(const Document& doc)
{
function<void(const IChapterContainer& container, const string& prefix)> PrintContainer;
PrintContainer = [&](const IChapterContainer& container, const string& prefix)
{
auto numChapters = container.GetChapterCount();
for (size_t i = 0; i < numChapters; ++i)
{
auto chapter = container.GetChapter(i);
auto intro = prefix + to_string(i + 1) + ".";
cout << intro << " " << chapter->GetTitle() << endl;
PrintContainer(*chapter, intro);
}
};
PrintContainer(doc, string());
}
void main()
{
ChapterSharedPtr someChapter;
{
auto document = Document::Create();
assert(document->GetDocument() == document);
auto chapter1 = document->AddChapter(document->CreateChapter("Chapter 1"));
assert(chapter1->GetParent() == document);
auto chapter2 = document->AddChapter(document->CreateChapter("Chapter 2"));
auto chapter3 = document->AddChapter(document->CreateChapter("Chapter 3"));
auto chapter21 = chapter2->AddChapter(document->CreateChapter("Chapter 2.1"));
auto chapter22 = chapter21->GetParent()->AddChapter(document->CreateChapter("Chapter 2.2"));
assert(chapter21->GetParent() == chapter2);
PrintTableOfContents(*document);
cout << "-------Moving Chapter 1 into Chapter 2.1" << endl;
chapter21->AddChapter(document->RemoveChapter(chapter1));
assert(chapter1->GetParent() == chapter21);
PrintTableOfContents(*document);
cout << "------- Removing Chapter 2.1" << endl;
chapter2->RemoveChapter(chapter21);
assert(!chapter21->GetParent());
PrintTableOfContents(*document);
someChapter = chapter2;
// Несмотря на то, что ссылка document уничтожится при выходе из блока,
// счетчик ссылок на документ не обнулится за счет aliasing-ссылки someChapter из внешнего блока
}
cout << "------- Printing the document obtained through the chapter.GetDocument()" << endl;
// Восстанавливаем ссылку на документ по имеющейся ссылке на одну из глав
auto doc = someChapter->GetDocument();
PrintTableOfContents(*doc);
// При выходе из этого блока счетчик ссылок на документ обнулится и он с главами будет выпилен
cout << "------- Leaving the function" << endl;
}
Вывод в stdout
1. Chapter 1
2. Chapter 2
2.1. Chapter 2.1
2.2. Chapter 2.2
3. Chapter 3
-------Moving Chapter 1 into Chapter 2.1
1. Chapter 2
1.1. Chapter 2.1
1.1.1. Chapter 1
1.2. Chapter 2.2
2. Chapter 3
— Removing Chapter 2.1
1. Chapter 2
1.1. Chapter 2.2
2. Chapter 3
— Printing the document obtained through the chapter.GetDocument()
1. Chapter 2
1.1. Chapter 2.2
2. Chapter 3
— Leaving the function
The document is being destroyed
The chapterChapter 2.2 has been destroyed
The chapterChapter 2.1 has been destroyed
The chapterChapter 3 has been destroyed
The chapterChapter 2 has been destroyed
The chapterChapter 1 has been destroyed
2. Chapter 2
2.1. Chapter 2.1
2.2. Chapter 2.2
3. Chapter 3
-------Moving Chapter 1 into Chapter 2.1
1. Chapter 2
1.1. Chapter 2.1
1.1.1. Chapter 1
1.2. Chapter 2.2
2. Chapter 3
— Removing Chapter 2.1
1. Chapter 2
1.1. Chapter 2.2
2. Chapter 3
— Printing the document obtained through the chapter.GetDocument()
1. Chapter 2
1.1. Chapter 2.2
2. Chapter 3
— Leaving the function
The document is being destroyed
The chapterChapter 2.2 has been destroyed
The chapterChapter 2.1 has been destroyed
The chapterChapter 3 has been destroyed
The chapterChapter 2 has been destroyed
The chapterChapter 1 has been destroyed
Оно же на гитхаб
+2
> Возможная польза от этого странного эффекта является предметом для обсуждения.
А я уже осознал пользу. Представим себе, в сегменте данных висит объект-затычка без данных (или с константными данными), которым управлять вообще не нужно. Оказывается, на этот объект можно тоже создавать shared_ptr.
А я уже осознал пользу. Представим себе, в сегменте данных висит объект-затычка без данных (или с константными данными), которым управлять вообще не нужно. Оказывается, на этот объект можно тоже создавать shared_ptr.
0
https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast
Красноречивый пример реального использования этого конструктора
0
Sign up to leave a comment.
Секретный конструктор std::shared_ptr