【问题标题】:Removing all the entries pointing to a deleted object from a map从地图中删除所有指向已删除对象的条目
【发布时间】:2012-06-03 16:15:24
【问题描述】:

这是我正在处理的代码:

class A
{
public:
    A(){}
    virtual ~A(){}
    void Log(){printf("Log A\n");}
};

int main(int argc, char**argv) 
{
    A* a = new A();

    a->Log(); // "Log A"

    map<int,A*> m;
    m[1] = a;
    m[2] = a;
    m[3] = a;

    m[1]->Log(); // "Log A"

    delete a;
    a = NULL;

    m[1]->Log(); // "Log A"

    return 0;
}

输出:

日志A
日志 A
日志A

我的问题:

  1. delete a之后调用m[1]-&gt;Log()不抛出异常只是偶然吗?
  2. 清除映射中指向已删除A 实例的所有条目的最佳方法是什么?我的意思是我希望所有m.find(1)m.find(2)m.find(3) 在删除a 后返回m.end()。任何建议都将不胜感激。

【问题讨论】:

  • 我已经用一个可能适合您要求的解决方案更新了我的答案。

标签: c++ pointers map


【解决方案1】:
  1. 是和不是。从技术上讲,这是未定义的行为,但通常(不要依赖于此)调用不访问成员的非虚拟方法似乎适用于大多数编译器,因为大多数编译器实现此调用没有取消引用this(这是无效部分)。因此,在标准看来,这是偶然的。对于大多数编译器来说,这是有意的(或者至少是处理函数调用的副作用)。

  2. 改用智能指针。要删除元素,您可以遍历地图并将每个值与您的值进行比较。当你到达一个时,使用erase迭代器在擦除后失效。

【讨论】:

  • @JamesKanze 我会使用shared_ptr。 Juanchopanza 推荐unique_ptr,但我不确定它是否可行。
  • @JamesKanze 刚刚看到您对他的回答的评论。我的想法完全正确。
  • 我已经编辑了第二个问题。您能否详细说明答案的第二部分?
  • @Meysamرهادربند 我想不出一个干净的解决方案(+您不能使用辅助映射,因为迭代器会失效)。我添加了我的想法。
  • shared_ptr 也会搞砸,因为它会使托管对象保持活动状态,而 OP 想要删除它并从映射中删除所有指向已删除内存的指针...
【解决方案2】:
  1. 取消引用已删除对象时发生的任何事情都是未定义的行为,因此即使出现异常也可能被视为“偶然”

  2. 最好的方法是将删除所指向的对象与您可以处理的对象的生命周期结合起来。因此,在您的情况下,您可以决定如果从地图中删除指针,则最好删除对象。为此,您可以使用 int 映射到 std::unique_ptr&lt;A&gt; 而不是原始指针之一。

编辑:更详细地查看要求后:

现在,由于您要删除映射类型指向已删除对象的元素,并且无法确定指针指向的内存是否已被删除,所以我看不到从映射中删除这些条目的简单方法除了在一个函数调用中完成所有操作。由于std::map 等人不喜欢std::remove_if,因此可以使用循环:

template <typename T1, typename T2>
void removeEntriesAndDelete(std::map<T1, T2*>& m, T2*& item) {
  for (auto i = m.begin(); i != m.end(); ) {
    if ( item == i->second) {
      m.erase(i++);
    } else {
      ++i;
    }
  }
  delete item;
  item=0;
} 

int main() {

  A* a = new A;
  std::map<int,A*> m;
  m[1] = a;
  m[2] = a;
  m[3] = a;
  std::cout << std::boolalpha;
  std::cout << a << ", " << bool(m[1]) << ", " << bool(m[2]) << ", " << bool(m[3]) <<"\n";

  removeEntriesandDelete(m, a);
 std::cout << a << ", " << bool(m[1]) << ", " << bool(m[2]) << ", " << bool(m[3]) <<"\n";
}

【讨论】:

  • 在他的例子中,他实际上有几个指向地图中同一个对象的指针,所以不能使用std::unique_ptr。 (他所说的也没有任何内容表明该地图在某些方面是指针的所有者。)
  • @JamesKanze 谢谢,我意识到我实际上并没有回答问题的第二部分。事实上,我想不出任何类型的智能指针可以解决 OP 的问题。我已经相应地编辑了我的答案。
  • 我不确定(我可能在这里错了),但我认为这不正确。在erase 之后迭代器不会失效吗?
  • @LuchianGrigore 只有被擦除元素的迭代器才会失效。
  • 是的,没错。它显然不适用于地图。
【解决方案3】:

一般来说,在某处注册的对象必须通知该对象在哪里注册 他们被录取了。在像您的示例代码这样的简单情况下,它是 遍历地图相对简单,擦除其中的每个元素 指向您的对象,但我认为您的实际用例较少 琐碎的。通常的解决方案(事实上,唯一真正有效的解决方案 在实践中)涉及观察者模式;当一个对象保存一个 指向您的对象的指针,直接或在地图或列表中或 无论如何,它也会与您的对象一起注册,在何时请求通知 你的对象被破坏了。您的对象保留了这些观察者的列表, 并将在其析构函数中通知他们。应用于一个简单的案例,如 你的,它看起来像很多不必要的代码,但在上下文中 出现这种模式的实际应用程序,并没有那么多。

【讨论】:

    【解决方案4】:

    正如你所说,它是靠运气工作的。

    要么使用前面提到的智能指针之类的东西,要么将映射和值处理封装在一个类中,在那里您可以有一个从映射中删除对象的方法删除该对象。

    【讨论】:

    • 他的类有一个虚拟析构函数这一事实表明他可能不得不处理地图中的多态对象。否则,当然,让地图包含值是要走的路。
    猜你喜欢
    • 2020-02-11
    • 1970-01-01
    • 1970-01-01
    • 2013-04-24
    • 2014-04-28
    • 1970-01-01
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多