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
(a
std::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
的构造函数和赋值也会抛出异常。