Вопросы по памяти поведения векторов
В последнее время я немного запутался в распределении памяти (де) std::vectors
Предположим, я получил нормальный вектор целого числа:std::vector<int> intv;
Когда я push_back
немного int
Это растет со временем. И когда я покидаю область действия (то есть) функции, она освобождается без необходимости дополнительных вызовов.
Отлично. Давайте рассмотрим другой пример:
struct foo_t{
std::string bar:
unsigned int derp;
}
void hurr(){
std::vector<foo_t> foov;
foo_t foo;
foo.bar = "Sup?";
foo.derp = 1337;
foov.push_back(foo);
}
Хорошо. Когда я звоню hurr()
вектор создается, foo_t
экземпляр создается, экземпляр заполняется и помещается в вектор. Поэтому, когда я покидаю функцию, вектор освобождается и содержимое (здесь один foo_t
) освобождается тоже?
Следующий пример:
struct foo_t{
std::string bar:
unsigned int derp;
}
std::vector<foo_t> hurr(){
std::vector<foo_t> foov;
foo_t foo;
foo.bar = "Sup?";
foo.derp = 1337;
foov.push_back(foo);
return foov;
}
В моем понимании, вектор и его содержимое живут в стеке, который (со временем) перезаписывается временем, а вектор, который я возвратил, и его содержимое будут бесполезны. Или он на самом деле возвращает копию вектора с копией его содержимого (требуется конструктор Copy для типа данных содержимого, если это не POD)?
И что-то очевидное:
struct foo_t{
std::string bar:
unsigned int derp;
}
std::vector<foo_t*> hurr(){
std::vector<foo_t*> foov;
foo_t foo = new foo_t;
foo->bar = "Sup?";
foo->derp = 1337;
foov.push_back(foo);
return foov;
}
Теперь я должен вручную перебрать вектор, удалить его содержимое, и тогда я смогу безопасно позволить вектору выпасть из области видимости, верно?
3 ответа
Этот пример:
struct foo_t{
std::string bar;
unsigned int derp;
};
void hurr(){
std::vector<foo_t> foov;
foo_t foo;
foo.bar = "Sup?";
foo.derp = 1337;
foov.push_back(foo);
}
После hurv()
законченный, foov
а также foo
оба освобождены.
std::vector<foo_t> hurr(){
std::vector<foo_t> foov;
foo_t foo;
foo.bar = "Sup?";
foo.derp = 1337;
foov.push_back(foo);
return foov;
}
результат std::vector<foo_t>
из hurr()
действует с 1 foo_t
в этом и оно действительно. return foov;
может вызвать копию конструктора std::vector<foo_t>
, и он имеет право не делать эту копию, см. elision
В любом случае, из C++11 вы можете написать это:
struct foo_t{
std::string bar;
unsigned int derp;
// we will copy the string anyway, pass-by-value
foo_t(std::string bar_, unsigned int d_)
: bar(std::move(bar_)), derp(d_) {}
};
std::vector<foo_t> hurr(){
std::vector<foo_t> foov;
// This is better, in place construction, no temporary
foov.emplace_back("Sup?", 1337);
// This require a temporary
foov.push_back(foo_t{"Sup?", 1337});
return foov;
}
И, в последнем примере, да, вы должны вручную выполнить итерации по вектору, удалить его содержимое, а затем я могу смело позволить, чтобы вектор выпал из области видимости, когда вы больше не хотите использовать результат hurr()
, (не в hurr()
)
foov.push_back(foo);
На самом деле, вы построили foo_v
и вы оттолкнули его назад, что на самом деле создал новый foo_v
и вызвал конструктор копирования с foov
в качестве показателя. использование emplace_back
если ты хочешь избежать этого.
return foov;
Компилятор может оптимизировать это с помощью оптимизации возвращаемого значения. Посмотрите на эту короткую программу, которую я сделал на coliru, в качестве примера. Обратитесь к другим отличным ответам в этом вопросе.
std::vector<foo_t*> foov;
/* add elements to foov with new */
Теперь я должен вручную перебрать вектор, удалить его содержимое, и тогда я смогу безопасно позволить вектору выпасть из области видимости, верно?
Да, вы делаете. По тем же причинам
int* a = new int();
Не буду delete a;
когда a
умирает.
Поэтому, когда я покидаю функцию, вектор освобождается и содержимое (здесь один
foo_t
) освобождается тоже?
Да. И если foo_t
был нетривиальный деструктор, это будет называться.
Или же он на самом деле возвращает копию вектора с копией его содержимого (требуется конструктор Copy для типа данных содержимого, если это не POD)?
Да, в этом случае он возвращает копию. Современные компиляторы могут вызывать конструктор копирования для std::vector
, который, в свою очередь, будет вызывать конструктор копирования содержащегося типа для каждого элемента. C++17 вводит гарантированную оптимизацию возвращаемого значения (RVO), поэтому конструктор копирования вашего вектора не будет вызываться. Тем не менее, если вы установите высокий уровень оптимизации, современный компилятор также может использовать RVO.
Теперь я должен вручную перебрать вектор, удалить его содержимое, и тогда я смогу безопасно позволить вектору выпасть из области видимости, верно?
Да ты прав. Подумайте об использовании умных указателей, если вы не хотите выполнять итерации вручную.