Общие сведения об общих указателях c ++

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

  1. Я понимаю, что когда создается общий указатель, указывающий на объект, я должен выделить память для структуры / класса, который будет содержать такую ​​информацию, как счетчик ссылок (изначально один) и, возможно, мьютекс для увеличения и уменьшения. Когда я использую, скажем, оператор =, чтобы другой общий указатель также указывал на этот объект, я также передаю указатель на эту структуру / класс этому новому указателю, чтобы я мог увеличивать счетчик. Мой вопрос: если я сделаю третий общий указатель, указывающий на этот объект (без использования конструктора копирования или оператора =), то этот указатель не будет знать о структуре и, следовательно, будет иметь счетчик ссылок 1, если я затем удалю указатель, счетчик достигнет 0, и объект будет уничтожен, когда на самом деле есть два других указателя для этого объекта?

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


person user2770808    schedule 09.05.2016    source источник
comment
Для (1) вы имеете в виду: shared_ptr<T> foo(new T); shared_ptr<T> bar = foo; shared_ptr<T> quux(foo.get());?   -  person Barry    schedule 09.05.2016
comment
Написание общих указателей - это проект не для новичков. Поэтому у нас стандартная версия. Я написал в блоге статью о типичных ошибках, которые люди допускают при реализации интеллектуальных указателей: Блог Локи Астари Кроме того, когда вы закончите, его стоит просмотреть на странице Проверка кода   -  person Martin York    schedule 09.05.2016
comment
Почему? Если это для того, чтобы научиться это делать, тогда ладно. В противном случае просто используйте std::unique_ptr и std::shared_ptr - не изобретайте велосипед.   -  person Jesper Juhl    schedule 09.05.2016


Ответы (5)


Я понимаю, что когда создается общий указатель, указывающий на объект, я должен выделить память для структуры / класса, который будет содержать такую ​​информацию, как счетчик ссылок (изначально один) и, возможно, мьютекс для увеличения и уменьшения.

Да, вам понадобится счетчик.
Мьютекс требуется только в том случае, если вы планируете многопоточность. Я бы сосредоточился на том, чтобы заставить работать счет, прежде всего, беспокоясь о блокирующих послесловиях.

Когда я использую, скажем, оператор =, чтобы другой общий указатель также указывал на этот объект, я также передаю указатель на эту структуру / класс этому новому указателю, чтобы я мог увеличивать счетчик.

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

Мой вопрос: если я сделаю третий общий указатель, указывающий на этот объект (без использования конструктора копирования или оператора =), то этот указатель не будет знать о структуре и, следовательно, будет иметь счетчик ссылок 1.

Ваше предположение верно. Вот почему при создании общего указателя не следует сохранять копию указателя. Это еще одна причина, по которой был введен std::make_shared(), он выделяет память и сразу же помещает ее в интеллектуальный указатель, поэтому в код пользователя не возвращается указатель RAW.

если я затем удалю указатель, счетчик достигнет 0 и объект будет уничтожен, хотя на самом деле есть два других указателя для этого объекта?

В этом и заключается проблема. Вот почему вы не должны создавать или передавать указатели RAW на объекты, которыми уже управляют.

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

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

person Martin York    schedule 09.05.2016
comment
Это не имеет ничего общего с make_shared(). Я всегда могу позвонить get() и обернуть это новым _3 _... - person Barry; 09.05.2016
comment
@Barry make_shared актуален, потому что это инструмент, который часто используется для предотвращения таких проблем, как описанные в OP. Это очень актуально. - person Xirema; 09.05.2016
comment
@Xirema Нет, это не так. make_shared существует для размещения блока управления вместе с объектом - за исключением одного распределения. Это не мешает мне писать auto p = std::make_shared<int>(42); std::shared_ptr<int> q(p.get()); - person Barry; 09.05.2016
comment
@Barry: Конечно. Но я думал. sharead_ptr<X> anObj(p); p может быть размещен где-то еще, а не в оболочке (как на это смотрит OP). Использование make_shared() решает эту проблему, а также делает вещи потенциально более эффективными. - person Martin York; 09.05.2016
comment
@Barry: Эти два преимущества не исключают друг друга. - person Martin York; 09.05.2016
comment
@Barry Но это предотвращает int * p = new int(10); shared_pointer<int> s1 = p; shared_pointer<int> s2 = p;, что вызовет UB, когда он выйдет за пределы области видимости. - person Xirema; 09.05.2016

Мой вопрос: если я сделаю третий общий указатель, указывающий на этот объект (без использования конструктора копирования или оператора =)

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

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

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

person Maxim Egorushkin    schedule 09.05.2016

Мой вопрос: если я сделаю третий общий указатель, указывающий на этот объект (без использования конструктора копирования или оператора =), то этот указатель не будет знать о структуре и, следовательно, будет иметь счетчик ссылок 1, если я затем удалю указатель, счетчик достигнет 0, и объект будет уничтожен, когда на самом деле есть два других указателя для этого объекта?

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

int * foo = new int(10); // set value to 10
{
    std::shared_ptr<int> a(foo);
    std::shared_ptr<int> b(foo)
} // uh-oh.  we call delete twice

Невозможно пометить указатель как принадлежащий. Человек, пишущий код, должен этого не делать. Мы можем обойти подобные вещи, создав указатель непосредственно в конструкторе, чтобы вы не могли передать его другому экземпляру.

std::shared_ptr<int> a(new int(10));

или вы можете использовать std::make_shared

auto b = std::make_shared<int>(10);

Теперь нам больше не нужно беспокоиться о том, чтобы передать необработанный указатель на другой общий указатель и удалить его из-под нас.

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

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

person NathanOliver    schedule 09.05.2016
comment
Грязный коварный человек все еще может вызвать .get() на общий указатель и построить на его основе новый. Но, как вы сказали, с этим ничего нельзя поделать, и тот, кто делает это, заслуживает всей боли, которую он получит. - person Jesper Juhl; 09.05.2016

  1. Ответ положительный. Если вы создаете общий указатель из простого указателя на объект, когда объект уже принадлежит другому общему указателю, то объект будет уничтожен, когда один счетчик ссылок достигнет 0, а остальные общие указатели останутся висящими - они продолжают указывать где раньше существовал объект.

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

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

person eerorika    schedule 09.05.2016

В типичной реализации, если вы создаете shared_pointer для объекта и создаете второй shared_pointer для того же объекта, не ссылаясь на уже созданный shared_pointer, у них обоих будет счетчик ссылок 1, удалите объект, когда какой-либо из них выйдет из строя. -scope и вызывать неопределенное поведение, когда другой пытается удалить объект.

Это [одна из] основных причин того, что в STL есть фабричные объекты, такие как std::make_shared и std::make_unique, поскольку обеспечение того, чтобы указатель на необработанный объект никогда не управлялся программистом, помогает предотвратить подобные ошибки.

Конечно, более умная реализация может попытаться отслеживать на статическом уровне все указатели, которые были созданы и поддерживаться, и может попытаться запоминать объекты. Но это связано с собственными рисками и проблемами, поэтому я не буду этого советовать.

Что касается многопоточности: это зависит от обстоятельств. Если все потоки создают shared_pointer на основе исходного shared_pointer объекта, и вы правильно используете свой mutex для поддержания счетчика ссылок, тогда все они должны без проблем увеличивать счетчик ссылок, и объект не должен удаляться только потому, что единственный поток прекратил его использовать.

person Xirema    schedule 09.05.2016
comment
Если я собираюсь получить отрицательные голоса, не могли бы вы хотя бы сказать мне, в чем проблема с моим ответом? - person Xirema; 09.05.2016