Почему make_unique для vector :: back () создает другую копию? - PullRequest
1 голос
/ 30 сентября 2019

Воспроизведите простой пример, чтобы продемонстрировать мой вопрос

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>

struct Party {
    Party(std::string value) : value_(value) {};
    std::string value_;
};

int main() {
    std::unordered_map<std::string, std::unique_ptr<Party>> map_;
    std::vector<Party> parties_;

    parties_.emplace_back("AAA");
    parties_.emplace_back("BBB");
    parties_.emplace_back("CCC");

    std::unique_ptr<Party> ptr = std::make_unique<Party>(parties_.back());

    ptr->value_ = "XXX";

    for (auto& p : parties_) {
        std::cout << p.value_ << std::endl; // print: AAA\nBBB\nCCC
    }

}

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

Но похоже, что при создании нового unique_ptr, parties_.back() фактически возвращается копия объекта вместо исходного объекта.

Как мне добиться того, чего я хочу здесь? Спасибо!

Ответы [ 3 ]

2 голосов
/ 30 сентября 2019

Эти два объекта

std::unordered_map<std::string, std::unique_ptr<Party>> map_;
std::vector<Party> parties_;

оба владеют экземплярами Party, поскольку std::vector всегда владеет его элементами, а std::unique_ptr предназначен исключительно для владения пуантиром. Вам необходимо решить, какой контейнер должен принадлежать сторонам и управлять их временем жизни. Пример: std::vector<Party> владеет экземплярами, затем вы можете использовать обычные указатели.

std::unordered_map<std::string, Party*> map_;
std::vector<Party> parties_;

Простые указатели подходят для тех случаев, когда они не включают семантику владения. После того как вы приняли это решение, исходный вопрос по parties_.back() и std::make_unique (который всегда создает новый экземпляр из-за цели std::unique_ptr) больше не является проблемой.

2 голосов
/ 30 сентября 2019

std::make_unique<Party>(something) по существу эквивалентно std::unique_ptr<Party>(new Party(something)), поэтому да, он создает новые копии Party объектов, потому что вы просили об этом.

В конечном итоге здесь unique_ptr не кажется правильнымвыбор: если ваш std::vector уже является единственным владельцем (и менеджером времени жизни) объектов, вам не нужно ничего делать, вы можете просто использовать простые указатели / ссылки. Однако имейте в виду, что их действительность привязана к правилам аннулирования ссылок std::vector - в частности, если он решит перераспределить (что может произойти, например, если вы сделаете push_back), все указатели / ссылки станут недействительными.

std::unordered_map<std::string, Party*> map_;
std::vector<Party> parties_;

parties_.emplace_back("AAA");
parties_.emplace_back("BBB");
parties_.emplace_back("CCC");

Party *ptr = &parties_.back();

// Notice: if you do parties_.emplace_back("DDD") here
// ptr may become invalid

ptr->value_ = "XXX";

for (auto& p : parties_) {
    std::cout << p.value_ << std::endl; // print: AAA\nBBB\nXXX
}

Если вы хотите быть изолированным от последствий перераспределения, но можете согласиться с тем, что std::vector является владельцем и, следовательно, определяет срок жизни ваших объектов, вы можете иметь std::vector<std::unique_ptr<Party>> (иопять же, держите простые указатели / ссылки на них повсюду)

std::unordered_map<std::string, Party*> map_;
std::vector<std::unique_ptr<Party>> parties_;

parties_.emplace_back(std::make_unique<Party>("AAA"));
parties_.emplace_back(std::make_unique<Party>("BBB"));
parties_.emplace_back(std::make_unique<Party>("CCC"));

Party *ptr = parties_.back().get();

// Notice: if you do parties_.emplace_back(std::make_unique<Party>("DDD"));
// ptr will remain valid

ptr->value_ = "XXX";

for (auto& p : parties_) {
    std::cout << p.value_ << std::endl; // print: AAA\nBBB\nXXX
}

Это гарантирует, что объекты выделяются независимо от вектора, но если они будут удалены из вектора, они будут удалены.


OTOH, если вы хотите разделить права владения между вектором и картой, вы можете захотеть std::shared_ptr (что, однако, не бесплатно, оно должно управлять подсчетом ссылок & co.):

std::unordered_map<std::string, std::shared_ptr<Party>> map_;
std::vector<std::shared_ptr<Party>> parties_;

parties_.emplace_back(std::make_shared<Party>("AAA"));
parties_.emplace_back(std::make_shared<Party>("BBB"));
parties_.emplace_back(std::make_shared<Party>("CCC"));

std::shared_ptr<Party> ptr = parties_.back();

// Notice: if you do parties_.emplace_back(std::make_unique<Party>("DDD"));
// ptr will remain valid, but it will still be valid even after
// parties_.pop_back() (ptr will keep the pointed object alive)

ptr->value_ = "XXX";

for (auto& p : parties_) {
    std::cout << p.value_ << std::endl; // print: AAA\nBBB\nXXX
}

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

2 голосов
/ 30 сентября 2019

std::make_unique<Party>(parties_.back()) создает новый объект, всегда. Это обертка для std::unique_ptr<Party>(new Party(parties_.back())). Обратите внимание, что parties_.back() сам по себе ничего не копирует, он возвращает ссылку.

В вашем коде ptr не нужно владеть Party, его точки: parties_ уже является владельцем. Просто используйте ссылку или необработанный указатель:

Party &lastParty = parties_.back();
lastParty.value_ = "XXX";
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...