Неожиданное значение, возвращаемое функцией use_count() shared_ptr при извлечении из вектора
Программа ниже выводит неожиданный use_count()
значение, когда разделяемый указатель печатается с использованием iterator
опровержение std::vector
:
#include<iostream>
#include<memory>
#include<vector>
class A;
typedef std::shared_ptr<A> sharedPtr;
typedef std::vector<sharedPtr> sharedPtrVect;
typedef sharedPtrVect::const_iterator vectItr;
class A
{
public:
A(int inp): m_Val(inp) { /*std::cout << "*** A ctor called: " << m_Val << " ***" <<std::endl;*/ }
~A() { /*std::cout << "### A dtor called: " << m_Val << " ###" <<std::endl; */}
int getVal() const { return m_Val; }
private:
int m_Val;
};
int main()
{
sharedPtrVect myVect1, myVect2;
vectItr myVectItr;
std::shared_ptr<A> tmpPtr;
for(int i = 1 ; i <= 5 ; i++ ) {
std::cout << "Pushed back: " << i << std::endl;
tmpPtr = std::make_shared<A>(i);
myVect1.push_back(tmpPtr);
}
myVectItr = myVect1.begin();
for( ; myVectItr != myVect1.end() ; ++myVectItr) {
std::cout << "-----------------------------" << std::endl;
std::cout << "Element number: " << (*myVectItr).get()->getVal() << std::endl;
std::cout << "Element use count: " << (*myVectItr).use_count() << std::endl;
std::cout << "-----------------------------" << std::endl;
}
return 0;
}
Вывод вышеуказанного кода:
Pushed back: 1
Pushed back: 2
Pushed back: 3
Pushed back: 4
Pushed back: 5
-----------------------------
Element number: 1
Element use count: 1
-----------------------------
-----------------------------
Element number: 2
Element use count: 1
-----------------------------
-----------------------------
Element number: 3
Element use count: 1
-----------------------------
-----------------------------
Element number: 4
Element use count: 1
-----------------------------
-----------------------------
Element number: 5
Element use count: 2 //I am not sure why or how this is 2?
-----------------------------
Я не понимаю как use_count()
последний элемент вектора равен 2. Разве это не должно быть 1, как другие? Я не создаю никаких копий общего указателя, хранящегося в последнем элементе вектора. Что мне здесь не хватает?
РЕДАКТИРОВАТЬ: У меня есть хороший опыт в C++98, но меньше опыта в C++11.
1 ответ
Разве это не должно быть 1, как другие? Я не создаю никаких копий общего указателя, хранящегося в последнем элементе вектора. Что мне здесь не хватает?
Но вы создаете копию. Вы push_back()
от tmpPtr
, push_back()
помещает копию своего аргумента в вектор, если только вы не скажете ему двигаться. (Подробнее об этом позже!)
Поэтому, что происходит для всех, кроме последнего элемента, это:
tmpPtr
содержит единственную ссылку на общий ресурс- Вы
push_back()
копия, так что конструктор копированияshared_ptr
увеличивает счетчик использования до 2 - Затем вы назначаете следующий элемент
tmpPtr
освобождает ссылку и тем самым уменьшает количество использований ресурса предыдущего элемента.
Но, конечно же, на последней итерации цикла нет последующего присваивания. Итак, в момент печати, tmpPtr
находится в области видимости и сохраняет ссылку на последний выделенный ресурс. Отсюда 1-й более высокий счет на последний элемент. Это кажется мне вполне ожидаемым.;)
Чтобы увидеть ожидаемые результаты, вам нужно либо уничтожить tmpPtr
после того, как вы скопируете его, но перед тем, как распечатать, или просто избегайте его копирования. Первое можно сделать, перенеся свою декларацию в for
цикл, как SirGuy указал в комментариях.
Тем не менее, ясно, что последний превосходит. Как мы это делаем? Ну, C++11 позволяет нам двигаться вместо этого. Итак, вы могли бы emplace_back( std::move(tmpPtr) )
, в котором move
приведёт к rvalue и, таким образом, вызовет move-конструктор vector
элемент. Это приведет к tmpPtr
выпустить свою ссылку после перемещения в vector
, эффективно гарантируя, что счетчик использования всегда равен 1. Это оставляет tmpPtr
(как и любой перемещенный объект) в допустимом, но неуказанном состоянии, т.е. полезно только для переназначения.
(Заметка: push_back()
достигнет того же самого, но я обычно предпочитаю использовать emplace_back()
везде, где это возможно, так как это более эффективно в других ситуациях, поэтому лучше по умолчанию.)
Конечно, вы можете затем объединить оба из них: объявить tmpPtr
в рамках for
цикл, и двигаться от него. Однако... вам даже не нужно tmpPtr
совсем! Это, кажется, не служит какой-либо полезной цели. Таким образом, вы можете просто не использовать его, а вместо этого напрямую emplace_back()
результат make_shared()
, Поскольку его возвращаемое значение будет r-значением, оно будет неявно перемещено в vector
; не брошен std::move
нужно.