Давайте возьмем следующий пример структуры данных (Node), которая представляет дерево дочерних узлов. Набор дочерних узлов для каждого объекта хранится в карте>
class Node;
typedef std::shared_ptr<Node> NodePtr;
class Node
{
std::map<const std::string, NodePtr> _childNodes;
void SomeOtherMethod();
public:
bool GetChildByKeyName(/*In*/ const std::string& key, /*Out*/ NodePtr& spChild)
{
bool result = false;
auto itor = _childNodes.find(key);
if (itor != _childNodes.end())
{
spChild = itor->second;
result = true;
SomeOtherMethod();
}
return result;
}
};
И следующий пример кода, как обычно называют его пользователи.
NodePtr spNode, spChildNode;
bool result;
...
result = spNode->GetChildByKeyName(strChildKeyName, spChildNode);
Все идет нормально.
Мне пришло в голову, что вызывающие стороны могут проходить по дереву без необходимости иметь дело с дополнительными переменными для каждой глубины в дереве.
NodePtr spNode;
bool result;
result = spNode->GetChildItem(strChildKeyName, spNode);
if (result)
spNode->GetChildItem(strSubKeyName, spNode);
В приведенном выше случае, если spNode является окончательный оставшаяся ссылка на объект, то меня беспокоит этот блок кода в методе GetChildItem:
spChild = itor->second;
result = true;
SomeOtherMethod();
Не нарушает ли назначение spChild (который на самом деле является экземпляром spNode вызывающего) случайный «этот» узел, так как последняя ссылка только что исчезла? (И, следовательно, вызывать другие методы после присваивания spChild опасно). У меня есть потенциальная ошибка здесь?
Я думаю, что решение состоит в том, чтобы просто добавить эту строку в верхней части вызова метода:
NodePtr spChildRef = spChild; // maintain a reference to the caller's original node during the scope of the method
Мысли?
Вы правы в том, что если внешний указатель spNode во втором примере является единственной ссылкой на корневой элемент, GetChildByKeyName заменит эту ссылку, что приведет к разрушению объекта (по сути, «удалить это»).
Я понимаю, что это может быть не полный код, и могут быть причины, по которым вы разработали его таким образом, но я бы лично предложил изменить интерфейс, чтобы возвращать найденного потомка вместо использования параметра out. (Вы все еще можете различить успех & не удалось найти ребенка путем проверки на ноль.)
Мало того, что фактический код поиска становится проще:
NodePtr GetChildByKeyName(/*In*/ const std::string& key)
{
auto itor = _childNodes.find(key);
if (itor != _childNodes.end())
{
SomeOtherMethod();
return itor->second;
}
return nullptr;
}
Затем вы также можете повторно использовать указатель на содержимое вашего сердца:
NodePtr spNode;
....
spNode = spNode->GetChildItem(strChildKeyName);
if (spNode)
spNode = spNode->GetChildItem(strSubKeyName);
+1 к @ Dentoid’s answer. Я не собираюсь дублировать его ответ здесь. Я покажу только, если ваш существующий код имеет проблемы.
Можно ли присвоить shared_ptr мусор
this
указатель?
Да, это так.
Я сделал тест, чтобы определить это: http://coliru.stacked-crooked.com/a/ef0d4f92902b4dee
Его вывод (отформатирован для наглядности):
spNode before: 0x15a1028
this: 0x15a1028 <---------------------------------------------------------|
spChild.get() == this |
|
spChild before: 0x15a1028 <-------- Notice: They're the same -------------|
Node 0x15a1028 destroyed. |
^---------------------------------------------------------------|
spChild after: 0x15a1078 |
|
SomeOtherMethod() @0x15a1028; size: 1 | |
spNode after: 0x15a1078 ^------------------------------------------------|
Node 0x15a1078 destroyed.
Так SomeOtherMethod()
вызывается на уже уничтоженном объекте, хотя среда выполнения не смогла обнаружить это.
Применяя ваш обходной путь, http://coliru.stacked-crooked.com/a/f0042d4b46fed340
spNode before: 0x19a7028
this: 0x19a7028
spChild.get() == this
spChild before: 0x19a7028
spChild after: 0x19a7078
SomeOtherMethod() @0x19a7028; size: 1
Node 0x19a7028 destroyed.
spNode after: 0x19a7078
Node 0x19a7078 destroyed.
У него больше нет проблем.