C++ 在代码的哪一行可能会发现异常/错误?
代码如下:C++ 在代码的哪一行可能会发现异常/错误?,c++,error-handling,exception-handling,C++,Error Handling,Exception Handling,代码如下: 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;
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
{
公众:
n元组(std::初始值设定项\u列表);
~n十倍(无效);
Ntuplet&operator=(常量Ntuplet&t);
私人:
大小;
T*m_对象;
};
模板
Ntuplet&Ntuplet::运算符=(常量Ntuplet&t)
{
如果(&t==此)
归还*这个;
删除[]个m_对象;
m_尺寸=t.m_尺寸;
m_对象=新的T[T.m_大小];
对于(int i=0;i
这是一次旧的考试。问题内容如下:
"
在哪一行可能会抛出异常;对象Ntuplet
将处于哪一种状态(初始、一致、不一致、未定义)?提出更好的实现类的方法,以避免异常/问题。”
我猜要么是m\u size=t.m\u size
,因为我认为t.m\u size
可能会有太大的值,但这不可能,因为那样的话t
对象怎么会存在(错误会出现得更早)。我唯一想到的另一件事是++i
,它可能超出了索引的范围
提前谢谢
编辑:“连贯”状态意味着对象处于一种没有矛盾属性的状态,但它不是我们想要的状态
“不连贯”意味着属性不是它们应该是的。例如,如果执行a++=b
,但=
运算符抛出错误,a
处于非相干状态,因为它是递增的,即使其余代码没有执行。在此状态下,析构函数可用
“Undefined”与上述相同,但析构函数也不可用。这里唯一可以抛出的代码是
new
(astd::bad_alloc
或从T
的构造函数中抛出的东西)或值赋值
如果new
抛出,则您的op=
没有完成,并且您的m_大小将与m_对象
不匹配(这将是一个指向死内存的悬空指针,该死内存用于保存可能其他大小的数组)
如果其中一个赋值抛出,则您的op=
没有完成,并且您的m_size
将是正确的,但是一些(或全部)数组元素将默认构造,而不是具有您想要的值
解决这个问题的方法是使用一个向量,这样你就不用担心它了。我不是说用向量实现Ntuplet
。。。我的意思是用std::vector
替换Ntuplet
,异常只能由非常特定的构造引发。任何未定义的行为,如取消引用无效指针、对C样式数组的超出范围访问、类型转换中的未定义行为,都不是例外
Ntuplet::operator=(const Ntuplet&t)
中唯一可以引发异常的是new[]
表达式(如果无法分配内存std::bad\u alloc
或者如果t
的默认构造函数引发任何异常)和循环中使用的t
类型的副本分配(取决于类型T
)
如果new
抛出异常,则m_objects
将悬空指针,因为它是delete[]
预先设置的。
m_size
将已经具有复制实例的大小。因此对象将不会处于任何正常状态。
假设Ntuplet
的析构函数实际上是delete[]
s分配给m_对象的内存
,那么稍后调用它将导致未定义的行为,因为存在双重空闲。
如果您不打算将Ntuplet
或m_objects
成员替换为负责异常安全的标准库实现,则此特定异常的一个解决方案是在修改任何mem之前将新的表达式的返回值保存到临时指针T*p
ber。在删除旧的m_对象
后,可以将临时对象分配给m_对象
如果循环中的赋值运算符引发异常,实例也将处于部分赋值状态。不过,之后调用析构函数应该可以(假设它只删除m_对象
)由于m_对象
指向一个新的
分配的数组。要使此异常安全,需要保留m_对象
中的所有旧值,因此循环应直接移动到new[]
之后,并应分配给p[]
,而不是m_对象[]
但是,这仍然会导致内存泄漏,因为如果分配循环抛出,分配的新[]
内存将不会被释放。因此,必须拦截任何异常以删除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;
}
模板
Ntuplet&Ntuplet::运算符=(常量Ntuplet&t)
{
如果(&t==此)
归还*这个;
T*p=新的T[T.m_尺寸];
试一试{
对于(大小i=0;i
我假设T
的析构函数不会抛出任何异常。原则上允许它们这样做,但这是不寻常的。@LightnessRacesinOrbit抱歉,我自己翻译了这个考试。我更新了帖子,我希望它更清楚:类型T
的构造函数和赋值也会抛出异常。