Общие указатели, не увеличивающие use_count

Я пытаюсь понять, как использовать std::shared_ptr в С++. Но это довольно запутанно, и я не понимаю, как создать несколько общих указателей, указывающих на один и тот же объект. Даже документация и онлайн-материалы не очень ясны.

Ниже приведен небольшой фрагмент кода, который я написал, чтобы попытаться понять поведение std::shared_ptr:

#include <iostream>
#include <memory>
using namespace std;

class Node
{
public:
    int key;
    Node()
    {
        key = 0;
    }
    Node(int k)
    {
        key = k;
    }
};

int main()
{
    Node node = Node(10);

    shared_ptr<Node> ptr1((shared_ptr<Node>)&node);
    cout << "Use Count: " << ptr1.use_count() << endl;

    // shared_ptr<Node> ptr2=make_shared<Node>(node);//This doesn't increase use_count
    shared_ptr<Node> ptr2((shared_ptr<Node>)&node);
    cout << "Use Count: " << ptr2.use_count() << endl;

    if (ptr1 == ptr2)
        cout << "ptr1 & ptr2 point to the same object!" << endl;

    if (ptr1.get() == ptr2.get())
        cout << "ptr1 & ptr2 point to the same address!" << endl;

    cout << "ptr1: " << ptr1 << "  "
         << "ptr2: " << ptr2 << endl;
    return 0;
}

Судя по полученным мной выводам, ptr1 и ptr2 указывают на один и тот же объект Node, но use_count равно 1 для них обоих. Даже использование std::make_shared не работает, и программа вылетает перед выходом.

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


person Dee Jay    schedule 12.01.2018    source источник
comment
(shared_ptr<Node>)&node неверно на нескольких уровнях.   -  person user7860670    schedule 12.01.2018
comment
Вы увеличиваете количество использований, говоря ptr2 = ptr1;, и вы НЕ ДОЛЖНЫ брать адрес локальной переменной, так как она попытается удалить в конце программы и произойдет ужасный сбой.   -  person Ken Y-N    schedule 12.01.2018
comment
В дополнение к комментарию @VTT вы создаете два отдельных указателя, которые являются общими указателями. Не обмен одним указателем.   -  person Ahmed Masud    schedule 12.01.2018
comment
Кстати, на этом справочном сайте есть хороший пример (но многопоточность делает его немного сложнее следовать, возможно).   -  person Ken Y-N    schedule 12.01.2018
comment
Хотя общие указатели создаются и передаются при создании ptr1 и ptr2, они являются временными и будут уничтожены или получат moved.   -  person sameerkn    schedule 12.01.2018
comment
Но что, если я не хочу делать ptr1=ptr2, а вместо этого хочу создать два shared_ptr с помощью объекта node? @KenY-N   -  person Dee Jay    schedule 12.01.2018
comment
Вы приехали с Явы? Просто пытаюсь понять, зачем вообще писать такой код и откуда берутся такие неправильные представления о shared_ptr.   -  person MikeMB    schedule 12.01.2018
comment
@DeeJay, если вы хотите убедиться, что несколько shared_ptr не имеют собственного use_count, взгляните на enable_shared_from_this   -  person PeterT    schedule 12.01.2018


Ответы (3)


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

Вам нужно указать, какие shared_ptr должны быть общими; use_count увеличивается, когда вы создаете shared_ptr из другого shared_ptr. например

shared_ptr<Node> ptr1 = make_shared<Node>(10);
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 1

shared_ptr<Node> ptr2(ptr1);
cout<<"Use Count: "<<ptr2.use_count()<<endl; // 2
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 2

Кстати: как подсказывают комментарии, опасно создавать shared_ptr, управляющий указателем на &node, который указывает на объект, размещенный в стеке. Следующий код может точно соответствовать вашим намерениям.

Node* node = new Node(10);

shared_ptr<Node> ptr1(node);
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 1

shared_ptr<Node> ptr2(ptr1);
cout<<"Use Count: "<<ptr2.use_count()<<endl; // 2
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 2
person songyuanyao    schedule 12.01.2018
comment
Но что, если я хочу создать два shared_ptr для одного и того же объекта? Например, на данном объекте node в коде? - person Dee Jay; 12.01.2018
comment
@DeeJay Ответ изменен. - person songyuanyao; 12.01.2018
comment
ptr2 и ptr1 — это два разных shared_ptr, указывающих на один и тот же объект Node. Я думаю, у вас может быть какая-то фундаментальная путаница в отношении того, что делает код? - person James Picone; 12.01.2018
comment
@songyuanyao Для ptr2, если я это сделаю: shared_ptr‹Node›ptr2(node); тоже не получится? - person Dee Jay; 12.01.2018
comment
@songyuanyao Почему use_count() по-прежнему равен 1 для ptr1 и ptr2, если оба указывают на один и тот же адрес? - person Dee Jay; 12.01.2018
comment
@DeeJay Нет, shared_ptr работает не так. Подумайте о конструкторе shared_ptr для shared_ptr<Node>ptr2(node);, как он проверяет, есть ли один или несколько shared_ptr, управляющих node? - person songyuanyao; 12.01.2018
comment
Да не сможет. Если только он не инициализирован с использованием другого shared_ptr. - person Dee Jay; 12.01.2018
comment
@DeeJay Да, это то, что должен делать shared_ptr. - person songyuanyao; 12.01.2018
comment
Таким образом, узел не будет освобожден до тех пор, пока значение use_count shared_ptr(s) не станет равным 0. - person Dee Jay; 12.01.2018
comment
@DeeJay Да. И обратите внимание, если у вас есть два отдельных shared_ptr, указывающих на один и тот же указатель, указатель будет освобожден в последний раз дважды. - person songyuanyao; 12.01.2018
comment
Не приведет ли это к SEGFAULT для второго shared_ptr? - person Dee Jay; 12.01.2018
comment
@DeeJay Это UB, все возможно. Это не обязательно должен быть SEGFAULT. - person songyuanyao; 12.01.2018
comment
Тогда какой смысл иметь shared_ptr(s). Я думал, что только последний shared_ptr освободит память. Если несколько shared_ptr(s) попытаются освободиться, то какая польза от них? - person Dee Jay; 12.01.2018
comment
@DeeJay shared_ptr не всемогущ. Вы должны соблюдать его предполагаемое использование. Образцы, показанные в моем ответе, безопасны, когда use_count, разделяемый shared_ptrs, становится 0, указатель будет уничтожен только один раз, последним shared_ptr. - person songyuanyao; 12.01.2018

shared_ptr<> также владеют объектом, на который они указывают. Это означает, что вы не можете создать shared_ptr из объекта стека. Чтобы создать shared_ptr, используйте new или make_shared:

shared_ptr<Node> ptr1(new Node(42));
shared_ptr<Node> ptr2 = make_shared<Node>();
shared_ptr<Node> ptr3 = make_shared<Node>(99);

Это все отдельные объекты и все имеют use_count()==1. Если вы хотите скопировать их, просто сделайте это:

shared_ptr<Node> copy1 = ptr1;

Теперь у ptr1 и copy1 есть use_count()==2.

person Matthias Bäßler    schedule 12.01.2018
comment
Итак, если я создам объект в куче, смогу ли я создать на нем shared_ptr(s).‹br/›Например‹br/›Node node=new Node(10);‹br/›shared_ptr‹Node› ptr1=make_shared‹Node›(узел);‹br/›shared_ptr‹Node› ptr2=make_shared‹Node›(узел);‹br/› Увеличит ли это use_count() как ptr1, так и ptr2? - person Dee Jay; 12.01.2018
comment
Если вы не удалите его самостоятельно: да. Узел * temp = новый узел (1); share_ptr‹Узел› ptr1(temp); - person Matthias Bäßler; 12.01.2018
comment
Но тогда: не создавайте 2 shared_ptr для одного и того же объекта кучи! - person Matthias Bäßler; 12.01.2018
comment
В чем может быть проблема, если я создам два shared_ptr для одного и того же объекта кучи? - person Dee Jay; 12.01.2018
comment
@DeeJay, первый правильно удалит объект, второй отправится в SEGFAULT - person Konstantin T.; 12.01.2018

есть несколько недоразумений, но я попытаюсь объяснить: в c++ объекты хранятся либо в стеке, либо в куче. объекты, созданные в стеке, будут уничтожены, а их память будет освобождена, как только вы покинете область, в которой они объявлены. это происходит полностью автоматически для вас. Например:

int bla()
{
    int a = 1;
    int b = 2;
    int result = a+b;
    return result;
}

в этом примере 3 intобъекта будут созданы, когда программа войдет в функцию bla, и будут уничтожены, когда функция вернется соответствующим образом.

тогда у вас есть куча. вы можете создавать объекты в куче через new. эти объекты переживают область, в которой они были созданы, но вы должны помнить об уничтожении объектов через delete. поскольку это распространенная ошибка и источник утечек памяти, стандартная библиотека предоставляет вспомогательные классы (например, shared_ptr, которые вы уже нашли) для решения этой проблемы. идея в том, что shared_ptrobject возьмет на себя ответственность за удаление данного объекта (который должен быть в куче (!) (да, вы можете обойти это с помощью специального удаления, но это немного более продвинуто, чем это объяснение)) как только сам shared_ptr будет уничтожен. как следует из названия, целью shared_ptr является совместное использование. чтобы поделиться, просто скопируйте файл shared_ptr. реализация shared_ptr имеет собственный конструктор копирования и оператор присваивания копии, который будет обрабатывать это, увеличивая счетчик использования и копируя адрес объекта, который обрабатывается в данный момент. и это единственный способ, которым объект shared_ptr узнает, есть ли другие экземпляры, управляющие тем же объектом, или нет. поэтому, чтобы увеличить количество использований, вам нужно сделать копию файла shared_ptr.

теперь позвольте мне объяснить, что пошло не так:

  1. ваш первоначальный Node node = Node(10);создается в стеке (как и целые числа выше). поэтому нет необходимости вручную управлять временем жизни. вы создали shared_ptr, управляющий адресом этого узла. это ошибка. как только вы покинете область действия, shared_ptr И сам объект узла (благодаря автоматизму в стеке) удалит объект узла. и это плохо.

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

давайте представим, что вы хотите, чтобы ваш объект Node действительно управлялся shared_ptr, вам нужно начать с одного shared_ptr, а затем скопировать из него столько, сколько вы хотите поделиться им. для создания первого у вас есть 2 возможности:

std::shared_ptr<int> mysharedpointer (new int(ANYNUMBER));
auto mysharedpointer = std::make_shared<int>(ANYNUMBER);

обычно вы предпочитаете второй вариант, так как он дает небольшое преимущество в производительности и большую безопасность при использовании в контексте вызова функции.

если теперь вам нужен второй shared_ptr, разделяющий владение с первым, вы можете сделать это, скопировав первый указатель:

auto mysecondsharedpointer = mysharedpointer;

и вуаля у вас есть копия, связанная с общим владением.

(все это немного упрощено, и я уверен, что что-то упустил. Не стесняйтесь завершать ответ)

person phön    schedule 12.01.2018
comment
Что, если я передаю shared_ptr в функцию? Будет ли работать обычный способ, например. 'void (shared_ptr‹int›a, shared_ptr‹int›b){}' . Будут ли параметры a и b в shared_ptr увеличивать значение use_count? - person Dee Jay; 06.02.2018
comment
учитывая вашу сигнатуру функции (void (shared_ptr<int> a, shared_ptr<int> b)), оба shared_ptr<int> берутся по значению. это означает, что они будут созданы (скопированы или перемещены) из переданных им аргументов. так что да, оба, a и b, увеличат количество использований на единицу. если вы передадите одно и то же shared_ptr в a и b, то, конечно, use_count увеличится на 2 (поскольку оба они владеют одним и тем же объектом) - person phön; 07.02.2018
comment
И будет ли use_count уменьшаться при возврате функции? Что делать, если я не хочу увеличивать use_count в функции? Просто использовать обычные указатели? - person Dee Jay; 07.02.2018
comment
да. если функция возвращается, область действия функции выходит, а ваши shared_ptr<int> a и shared_ptr<int> b уничтожаются. в деструкторе shared_ptr<> соответствующий use_count будет уменьшен. - person phön; 07.02.2018
comment
если вы хотите избежать увеличения use_count, вы можете: 1. переместить shared_ptr при вызове вашей функции. таким образом, вызывающий объект потеряет право собственности, а объект будет удален после функции. 2. измените сигнатуру вашей функции на получение ссылки на shared_ptr. вам следует избегать этого 3. измените сигнатуру вашей функции, чтобы она брала ссылку на объект, на который указывает shared_ptr (в данном случае int): void (int& a, int& b). на стороне вызова вам затем нужно разыменовать shared_pointer 4. так же, как 3, но с невладельческим необработанным указателем на int (int*). использовать, если аргумент может быть нулевым - person phön; 07.02.2018
comment
Последний вопрос: Shared_ptr поточно-безопасен . Я имею в виду, является ли операция увеличения и уменьшения use_count атомарной? Существует ли вероятность того, что в многопоточном сценарии поток free вызывается для объекта потоком, даже если другой поток может иметь ссылку на указанный объект. Это может произойти, если операции увеличения/уменьшения не являются атомарными. - person Dee Jay; 07.02.2018
comment
да, деструктор shared_ptr является потокобезопасным для всех потоков. ни один поток не украдет ваш указанный объект. НО сам указанный объект не является потокобезопасным. скажем, у вас есть shared_ptr<int>, тогда вы не можете прочитать int в потоке A и изменить его в потоке B без какой-либо другой синхронизации - person phön; 07.02.2018
comment
Итак, если поток-A имеет ссылку на объект O, нет никаких шансов, что другой поток-B освободит указанный объект O? По ссылке я имею в виду, что оба потока A и B используют shared_ptr‹›. - person Dee Jay; 07.02.2018
comment
это правильно. последний shared_ptr<> живой уберет. подсчет ссылок выполняется атомарным способом. нет условий гонки ;-) если вы хотите прочитать немного больше: en.cppreference. com/w/cpp/memory/shared_ptr - person phön; 07.02.2018
comment
Круто, пытаемся устранить ошибку двойного бесплатного в многопоточной среде! :П - person Dee Jay; 07.02.2018