这是代码:
template <typename T> class Ntuplet
{
public:
Ntuplet(std::initializer_list<T> s);
~Ntuplet(void);
Ntuplet<T>& operator=(const Ntuplet<T>& t);
private:
size_t m_size;
T* m_objects;
};
template<typename T>
Ntuplet<T>& Ntuplet<T>::operator=(const Ntuplet<T>& t)
{
if (&t == this)
return *this;
delete [] m_objects;
m_size = t.m_size;
m_objects = new T[t.m_size];
for(int i = 0; i < m_size; ++i)
m_objects[i] = t.m_objets[i];
return *this;
}
这是来自旧考试。问题显示为:
”
在哪一行可能会引发异常;对象Ntuplet
处于哪种状态(初始,相干,不相干,未定义)?提出一种更好的方法来实现该类,以避免出现异常/问题。”
我的猜测是在m_size = t.m_size
上,因为我认为t.m_size
的值可能太大,但这不可能,因为那样的话t
对象将如何存在(该错误会更早出现)。唯一想到的另一件事是++i
可能超出索引范围。.?
预先感谢
编辑:“相干”状态,表示对象处于没有矛盾属性的状态,但不处于我们想要的状态。
“不一致”表示属性不是应有的属性。例如,如果您执行a++ = b
,但=
运算符抛出错误,则a
处于非相干状态,因为即使其余代码未得到被执行。在这种状态下,析构函数可用。
“未定义”与上面的相同,但是析构函数也不可用。
答案 0 :(得分:0)
这里唯一可以抛出的代码是new
(std::bad_alloc
或T
的构造函数抛出的东西)或值分配。
如果new
抛出,则您的op=
未完成,并且您的m_size
将与m_objects
不匹配(这将是悬而未决的)指向死内存的指针,该死内存用于保存可能具有其他大小的数组。
如果其中一项分配抛出,则您的op=
不会完成,并且您的m_size
将是正确的,但是某些(或全部)数组元素将是默认构造的,而不是具有您想要的值。
解决此问题的方法是使用向量,这样您就不必担心它。我并不是说要使用向量来实现Ntuplet
……我的意思是用Ntuplet
替换std::vector
。
答案 1 :(得分:0)
异常只能由非常特定的构造引发。诸如取消引用无效的指针,对C样式数组的超出范围的访问,类型转换中的未定义行为之类的未定义行为也不例外。
Ntuplet<T>::operator=(const Ntuplet<T>& t)
中唯一可以引发异常的事物是new[]
表达式(如果无法分配内存std::bad_alloc
或T
的默认构造函数抛出任何异常循环中使用的T
类型的副本分配(取决于T
是什么类型)。
如果new
抛出异常,则m_objects
将悬挂指针,因为它是事先delete[]
插入的。
m_size
将具有复制实例的大小。因此,对象将不会处于任何健全状态。
假设Ntuplet
的析构函数实际上delete[]
分配了为m_objects
分配的内存,然后再调用它会由于double-free而导致未定义的行为。
如果您不打算用负责异常安全性的标准库实现替换Ntuplet
或m_objects
成员,那么针对此特定异常的一种解决方案是保存{的返回值修改任何成员之前,将{1}}表达式表示为临时指针new
。然后,在删除旧的T* p
之后,可以将临时文件分配给m_objects
。
如果循环中的赋值运算符引发异常,则实例也将处于部分分配状态。但是,随后调用析构函数应该很好(假设它仅删除m_objects
),因为m_objects
指向分配给m_objects
的数组。要使此异常安全,需要将所有旧值保留在new
中,因此循环应直接移至m_objects
之后,并且应将其分配给new[]
而不是p[]
。
但是这仍然会导致内存泄漏,因为如果分配循环抛出,m_objects[]
分配的内存将不会被释放。因此,必须拦截任何异常才能删除new[]
:
p
我假设template<typename T>
Ntuplet<T>& Ntuplet<T>::operator=(const Ntuplet<T>& t)
{
if (&t == this)
return *this;
T* p = new T[t.m_size];
try {
for(size_t i = 0; i < t.m_size; ++i)
p[i] = t.m_objects[i];
} catch (...) {
delete[] p;
throw;
}
delete [] m_objects;
m_objects = p;
m_size = t.m_size;
return *this;
}
的析构函数不会引发任何异常。原则上允许他们这样做,但这是不寻常的。