【问题标题】:Construct one list from element of another list without adding to heap memory从另一个列表的元素构造一个列表而不添加到堆内存
【发布时间】:2019-04-28 11:31:20
【问题描述】:

我正在开发一个内存关键应用程序。我先生成一个列表,例如(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++ 中完成同样的任务?

【问题讨论】:

  • 在 c++ 中你也可以使用指针,但最好使用智能指针
  • 这是个问题。如何通过引用向 std::list 添加元素?
  • 与您的问题无关,但您确定std::list 是最适合您的容器吗?你用它做什么?导致您在默认容器std::vector 上使用列表的要求是什么?如果内存很重要,请注意 X 节点的 std::list 比 X 元素的 std::vector 使用 更多 内存(因为私有节点结构本身)。也许您应该改用std::deque(以获取一些列表和向量)?
  • @Someprogrammerdude 因为在我的应用程序中可能会插入数据。
  • 要拼接列表吗?

标签: c++ stdlist


【解决方案1】:

您可以使用std::reference_wrapper,它可以作为参考并且可以存储在容器中。

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

请注意,尽管std::reference_wrapper 具有对其值类型sometimes you still have to use its explicit get method 的隐式转换运算符。例如,您必须使用

(*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
}

【讨论】:

    【解决方案2】:

    std::list&lt;T&gt; 拥有它的元素。这是使用标准容器的一个主要动机。他们管理元素的生命周期。如果你希望容器不拥有它的元素,那么不要让它存储Ts。有几种选择。使用std::list,您的优势在于std::lists 的迭代器不会那么容易失效(例如std::vector 可以重新分配和复制每个插入上的所有元素,std::list 不是这种情况)。因此,您可以使用迭代器:

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

    请注意,如果nodes 使其迭代器无效,例如通过擦除元素,那么sub_nodes 可能包含无效条目。

    【讨论】:

      【解决方案3】:

      您可以创建一个字符串指针列表。

      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 中的指针相同。现在更改任何指针都会在这两个地方反映出来。例如,下面的代码将 slist2 中的“Welcome”和“Hi”替换为“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;
      }
      

      【讨论】:

      • 这种方法是否适用于 std::string 以外的其他对象或结构?
      • 是的,我认为我们应该使用指针(在这种情况下它是字符串类型,但也可以是其他类型)
      • 请使用智能指针,否则代码不是异常安全的,您必须在列表被破坏之前手动删除这些字符串。此外,*s = "abc" 足以更改字符串。 std::string 在 C++ 中是可变的。
      • 你在这里有未定义的行为。您正在取消引用由以前的 delete 无效的指针。 碰巧您的实现使用了相同的地址来分配刚刚释放的字符串。
      • @Caleth - 我不认为这是未定义的行为。指针在删除后没有被取消引用。恕我直言,删除 ptr; ptr = 新的东西;不是未定义的行为。 *ptr 本来是未定义的,但分配后 ptr 指向有效内存。
      猜你喜欢
      • 2021-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-08
      • 1970-01-01
      • 1970-01-01
      • 2022-07-21
      相关资源
      最近更新 更多