繁体   English   中英

C++:在抛出/捕获异常时,异常对象何时被破坏?

[英]C++: In throwing/catching exceptions, when are the exception objects destructed?

我遇到了这篇文章: 最佳实践:按值抛出,按常量引用捕获,并对异常 object 何时被破坏感到好奇。

这是我的异常结构,就像文章中的第二个示例一样(带有一些 std::cout 的)。

struct BASE_EX {
    static int id;
    BASE_EX() { 
        ++id; 
        std::cout << "constructing BASE_EX " << id << std::endl; 
    }
    virtual std::string const what() const { return "BASE_EX " + std::to_string(id); }
    ~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; }
};

struct DERIVED_EX : BASE_EX {
    static int derived_id;
    DERIVED_EX() {
        ++derived_id; 
        std::cout << "constructing DERIVED_EX " << derived_id << std::endl; 
    }
    std::string const what() const { return "DERIVED_EX " + std::to_string(derived_id); }
    ~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << derived_id << std::endl; }
};

int BASE_EX::id = 0;
int DERIVED_EX::derived_id = 0;

当运行这个主 function 时,通过 const& 捕获:

int main() {
    try {
        try {
            throw DERIVED_EX();
        } catch(BASE_EX const& ex) {
            std::cout << "First catch block: " << ex.what() << std::endl;
            throw ex;
        }
    } catch(BASE_EX const& ex) {
        std::cout << "Second catch block: " << ex.what() << std::endl;
    }
}

我有

constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: DERIVED_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1

问题一:如果BASE_EX在第二次catch之前就被破坏了,它是如何被catch的?

问题 2:为什么破坏比建设多?

问题3:当我将两个catch都改为catch by value而不是const&时,为什么output变成了

constructing BASE_EX 1
constructing DERIVED_EX 1
First catch block: BASE_EX 1
destructing BASE_EX 1
destructing DERIVED_EX 1
destructing BASE_EX 1
Second catch block: BASE_EX 1
destructing BASE_EX 1
destructing BASE_EX 1

任何关于 cpp try-catch 如何在幕后工作的推荐读物都会很棒。 谢谢。

如果要标记 object 的构造函数,请标记所有构造函数;)

struct BASE_EX {
    static int count;
    int id;
    BASE_EX() : id(count++) { 
        std::cout << "constructing BASE_EX " << id << std::endl; // usually std::endl is unnecessary (it's just "\n" followed by std::flush), but since we're playing with crashes it's probably a good idea
    }
    BASE_EX(BASE_EX const &other) : id(count++) {
        std::cout << "copying BASE_EX " << other.id << " as BASE_EX " << id << std::endl;
    }
    // implicit move constructor not declared
    virtual std::string what() const { return "BASE_EX " + std::to_string(id); } // marking by-value return as const does absolutely nothing
    ~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; } // reminder that base class destructors should generally be virtual; not required in this case
};
int BASE_EX::count = 0;

struct DERIVED_EX : BASE_EX {
    static int count;
    int id;
    DERIVED_EX() : BASE_EX(), id(count++) {
        std::cout << "constructing DERIVED_EX " << id << std::endl; 
    }
    DERIVED_EX(DERIVED_EX const &other) : BASE_EX(other), id(count++) {
        std::cout << "copying DERIVED_EX " << other.id << " as DERIVED_EX " << id << std::endl;
    }
    // implicit move constructor not declared
    std::string what() const override { return "DERIVED_EX " + std::to_string(id); }
    ~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << id << std::endl; }
};
int DERIVED_EX::count = 0;

你得到

constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
Second catch block: BASE_EX 1
destructing BASE_EX 1

第一次throw将异常 object 设置为DERIVED_EX 0 内部catch获取对该异常 object 的BASE_EX 0基 class 子对象的引用。 由于whatvirtual ,调用它会导致DERIVED_EX报告它的类型。 但是,当你再次throw ex时, ex只有 static 类型BASE_EX ,所以新的异常 object 被选为BASE_EX ,它是通过仅复制第一个异常 ZA8CFDE63311CBD4B6662 的BASE_EX部分创建的当我们退出第一个catch时,第一个异常 object 被破坏,外部 catch 接收新的BASE_EX object。 因为它确实BASE_EX ,而不是DERIVED_EX ,所以调用what了这一点的内容。 如果你同时使用catch的值,你会得到

constructing BASE_EX 0
constructing DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
First catch block: BASE_EX 1
copying BASE_EX 1 as BASE_EX 2
destructing BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
copying BASE_EX 2 as BASE_EX 3
Second catch block: BASE_EX 3
destructing BASE_EX 3
destructing BASE_EX 2

当您按值catch时,将复制异常 object 以初始化捕获参数。 在执行这样的catch块期间,有两个对象表示异常:没有名称的实际异常 object,以及为可命名的catch块制作的异常副本。 第一个副本是将第一个异常 object 复制到第一个catch的参数。 第二个副本是该参数的副本,作为第二个例外 object。 第三个是将异常 object 复制到第二个catch的参数中。 当我们进入第一个catch时,异常的DERIVED_EX部分已被切掉。 catch参数在每个catch结束时按照通常的范围规则被销毁。 每当相应的catch块退出时,异常对象就会被销毁。

通过不按值获取异常并且不使用throw <catch-parameter>重新抛出异常,您可以避免复制问题和切片问题。

int main() {
    try {
        try {
            throw DERIVED_EX();
        } catch(BASE_EX const &ex) {
            std::cout << "First catch block: " << ex.what() << std::endl;
            throw;
        }
    } catch(BASE_EX const &ex) {
        std::cout << "Second catch block: " << ex.what() << std::endl;
    }
}

constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
Second catch block: DERIVED_EX 0
destructing DERIVED_EX 0
destructing BASE_EX 0

异常 object 在第一个catch结束时不会被销毁,因为它以throw退出,这表明相同的异常 object 将用于匹配更多的catch子句。 它没有被复制到一个新的异常 object 中,并像throw ex所要求的那样被销毁。

有关规则的详细说明,请参阅cppreference

了解它们何时被破坏的最好方法是假装catch类是 function:

catch(BASE_EX const& ex) {
    std::cout << "Second catch block: " << ex.what() << std::endl;
}

做一个临时调整,假装这是一个function,而ex只是这个function的一个参数:

void exception_handler(BASE_EX const& ex) {
    std::cout << "Second catch block: " << ex.what() << std::endl;
}

每当这个伪函数参数被破坏时,异常 object 就会被破坏,如果输入异常处理程序就好像它是一个普通的 function 调用,在这里。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM