对象能否从标准 C++ 容器中擦除自身?
Can an object erase itself from a standard C++ container?
代码如下
#include <iostream>
#include <map>
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
m.erase(i);
}
};
int main()
{
std::map<int, foo> m;
m.emplace(1, foo() );
std::cout << m.size() << std::endl;
m[1].kill(m, 1);
std::cout << m.size() << std::endl;
}
编译时没有警告 (g++),执行时没有错误,根据输出判断 kill
方法从映射中删除了 foo
对象。但是,我觉得这实际上可能是未定义的行为。似乎在 kill
方法中 m.erase(i)
this
行之后不再指向有效对象。
C++ 标准对此有何规定?
当您输入 kill
时,m[1]
(来自 m[1].kill(m, 1);
)语句已被完全评估为您正在调用 kill
的 foo
对象上。
然后你 m.erase(i);
最终销毁当前对象 foo
。
就您在 kill
函数 return 之前绝对没有使用当前对象 (this
) 编写语句而言,这是完全可以接受和安全的(正如Auriga and Barry 引用的帖子)。即使当前对象不再存在,您的函数也会 return 安全地从堆栈中取出,据我所知没有理由失败。
举个例子,这将以未定义的行为结束,绝不能这样做:
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
m.erase(i);
cout << attribute; // don't do that! current foo object does not exist anymore
}
int attribute;
};
那么假设你正在做的事情是有风险的,但如果你做得好,那就是有效和安全的。
作为示例,这将以定义的行为结束并且可以完成:
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
int theAttribute = attribute;
m.erase(i);
cout << theAttribute; // OK!
}
int attribute;
};
无论如何,让方法删除当前对象可能不是一个好的做法(特别是如果另一个开发人员稍后修改代码......他很容易使上面的第一个示例崩溃)。至少在代码中放一个明确的注释,告诉当前对象可能已经被销毁(注意 kill
可能会销毁当前对象,另一个,或者 none...取决于 m
内容和 i
):
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
m.erase(i);
// careful! current object could have been destroyed by above statement and may not be valid anymore! Don't use it anymore!
}
};
这根本不安全。如果多次调用 m.erase(i)
将避免尝试擦除同一个对象,但如果多次调用 m[1].kill(m, 1)
是未定义的行为。为了稍微安全一点,m.at(1).kill(m, 1)
会抛出一个out_of_range
错误。
代码如下
#include <iostream>
#include <map>
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
m.erase(i);
}
};
int main()
{
std::map<int, foo> m;
m.emplace(1, foo() );
std::cout << m.size() << std::endl;
m[1].kill(m, 1);
std::cout << m.size() << std::endl;
}
编译时没有警告 (g++),执行时没有错误,根据输出判断 kill
方法从映射中删除了 foo
对象。但是,我觉得这实际上可能是未定义的行为。似乎在 kill
方法中 m.erase(i)
this
行之后不再指向有效对象。
C++ 标准对此有何规定?
当您输入 kill
时,m[1]
(来自 m[1].kill(m, 1);
)语句已被完全评估为您正在调用 kill
的 foo
对象上。
然后你 m.erase(i);
最终销毁当前对象 foo
。
就您在 kill
函数 return 之前绝对没有使用当前对象 (this
) 编写语句而言,这是完全可以接受和安全的(正如Auriga and Barry 引用的帖子)。即使当前对象不再存在,您的函数也会 return 安全地从堆栈中取出,据我所知没有理由失败。
举个例子,这将以未定义的行为结束,绝不能这样做:
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
m.erase(i);
cout << attribute; // don't do that! current foo object does not exist anymore
}
int attribute;
};
那么假设你正在做的事情是有风险的,但如果你做得好,那就是有效和安全的。
作为示例,这将以定义的行为结束并且可以完成:
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
int theAttribute = attribute;
m.erase(i);
cout << theAttribute; // OK!
}
int attribute;
};
无论如何,让方法删除当前对象可能不是一个好的做法(特别是如果另一个开发人员稍后修改代码......他很容易使上面的第一个示例崩溃)。至少在代码中放一个明确的注释,告诉当前对象可能已经被销毁(注意 kill
可能会销毁当前对象,另一个,或者 none...取决于 m
内容和 i
):
struct foo
{
void kill(std::map<int, foo>& m, int i)
{
m.erase(i);
// careful! current object could have been destroyed by above statement and may not be valid anymore! Don't use it anymore!
}
};
这根本不安全。如果多次调用 m.erase(i)
将避免尝试擦除同一个对象,但如果多次调用 m[1].kill(m, 1)
是未定义的行为。为了稍微安全一点,m.at(1).kill(m, 1)
会抛出一个out_of_range
错误。