这是代码:
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
处于不连贯状态,因为即使未执行其余代码,它也已递增。在这种状态下,析构函数可用。“未定义”与上面的相同,但析构函数也不可用。
最佳答案
异常只能由非常具体的构造引发。任何未定义行为,例如取消引用无效的指针,对C样式数组的超范围访问,类型转换中的未定义行为,都不异常(exception)。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
成员,则针对此特定异常的一种解决方案是在修改之前将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;
}
我假设
T
的析构函数不会引发任何异常。原则上允许他们这样做,但这是不寻常的。
关于c++ - 在此代码的哪一行可能找到异常/错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53643530/