Построить один список из элемента другого списка без добавления в кучу памяти - PullRequest
0 голосов
/ 28 апреля 2019

Я занимаюсь разработкой приложения, критичного к памяти. Сначала я создаю список, например (C ++ 11):

std::list<string> nodes({"Hello","Welcome", "Hi", "World"});

Теперь мне нужно создать меньший список со вторым и третьим элементами «узлов». Наивно я бы сделал:

std::list<string> sub_nodes;
sub_nodes.push_back(*std::next(nodes.begin(),1));
sub_nodes.push_back(*std::next(nodes.begin(),2));

Но это явно выделяет память в куче для sub_nodes, выделяя новую память.

*(sub_nodes.begin()) = "NotWelcome"; //changing first element
std::list<string>::iterator it;
for(it=nodes.begin();it!=nodes.end();++it) cout<<*it<<'\t';
cout<<'\n';
//Hello Welcome Hi World
for(it=sub_nodes.begin();it!=sub_nodes.end();++it)
cout<<*it<<'\t';
cout<<'\n';
//NotWelcome Hi

Что я хочу сделать, так это чтобы элементы sub_nodes занимали тот же адрес элементов nodes, из которого они были созданы. Другими словами, я хочу, чтобы изменения, внесенные в элементы sub_nodes, отражались в этих элементах в nodes и наоборот. В связанных списках C это было бы просто, поскольку узлы списков в основном являются указателями. Как бы я сделал то же самое в C ++?

Ответы [ 3 ]

1 голос
/ 29 апреля 2019

Вы можете использовать std::reference_wrapper, который работает как ссылка и может храниться в контейнере.

std::list<std::reference_wrapper<std::string>> sub_nodes; // #include <functional>

Обратите внимание: хотя std::reference_wrapper имеет неявный оператор преобразования для своего типа значения, иногда вам все равно придется использовать его явный get метод . Например, вы должны использовать

(*sub_nodes.begin()).get() = "NotWelcome"; 

для изменения ссылочного объекта.

Ниже приведен полный рабочий пример:

#include <iostream>
#include <functional>
#include <list>
#include <string>

int main()
{
    std::list<std::string> nodes({"Hello","Welcome", "Hi", "World"});
    std::list<std::reference_wrapper<std::string>> sub_nodes;
    sub_nodes.push_back(*std::next(nodes.begin(), 1));
    sub_nodes.push_back(*std::next(nodes.begin(), 2));
    (*sub_nodes.begin()).get() = "NotWelcome"; 
    for (auto it = nodes.begin(); it != nodes.end(); ++it) std::cout << *it << '\t';
    std::cout << '\n';
    // Hello NotWelcome Hi World
    for (auto it = sub_nodes.begin(); it != sub_nodes.end(); ++it)
        std::cout << (*it).get() << '\t';
    std::cout << '\n';
    // NotWelcome Hi
}
1 голос
/ 28 апреля 2019

A std::list<T> владеет своими элементами.Это одна из основных причин использования стандартных контейнеров.Они управляют жизнью своих стихий.Если вы хотите, чтобы контейнер не владел своими элементами, не заставляйте его хранить T s.Есть несколько вариантов.С std::list вы получаете преимущество, заключающееся в том, что итераторы std::list не могут быть легко признаны недействительными (например, std::vector может перераспределять и копировать все элементы на каждой вставке, не в случае std::list).Следовательно, вы можете использовать итераторы:

std::list< std::list<std::string>::iterator > sub_nodes;
sub_nodes.push_back( nodes.begin() );
// etc..

Только будьте осторожны, если nodes сделает недействительными его итераторы, например, удалив элемент, то sub_nodes может содержать недопустимые записи.

0 голосов
/ 28 апреля 2019

Вы можете создать список строковых указателей.

std::list<std::string*> slist1;

slist1.push_back(new std::string("Hello"));
slist1.push_back(new std::string("Welcome"));
slist1.push_back(new std::string("Hi"));
slist1.push_back(new std::string("World"));

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

auto it = slist1.begin();
std::list<std::string*> slist2(std::next(it, 1), std::next(it, 3));

Эти slist2 указатели списка - те же самые указатели, что ив slist1.Теперь изменение любого указателя будет отражено в обоих местах.Например, приведенный ниже код замените «Welcome» и «Hi» в slist2 на «abc».Изменения отражаются и в slist1, так как указатели совпадают.

for (auto &s: slist2) {
        std::cout << *s << "\n";
       *s = "abc";
 }

for (auto &s: slist1) {
        std::cout << *s << " "; //prints Hello abc abc World
    }

Обратите внимание, что этот код не является безопасным для исключения, и список придется удалить вручную.Для обеспечения безопасности исключений и автоматического уничтожения, пожалуйста, используйте умные указатели.Ниже приведен пример использования shared_ptr.

#include <iostream>
#include <memory>
#include <string>
#include <list>
int main()
{
    std::list<std::shared_ptr<std::string>> slist1;

    slist1.push_back(std::make_shared<std::string>("Hello"));
    slist1.push_back(std::make_shared<std::string>("Welcome"));
    slist1.push_back(std::make_shared<std::string>("Hi"));
    slist1.push_back(std::make_shared<std::string>("World"));

    auto it = slist1.begin();
    std::list<std::shared_ptr<std::string>> slist2(std::next(it, 1), std::next(it, 3));
    for (auto &s: slist2) {
        std::cout << *s << " ";
        *s = "abc";
    }

    for (auto &s: slist1)
        std::cout << *s << " ";  //prints Hello abc abc World
    return 0;
}
...