代码之家  ›  专栏  ›  技术社区  ›  Daniel Langr

在C中新建[]/delete[],并抛出构造函数/析构函数++

  •  1
  • Daniel Langr  · 技术社区  · 7 年前

    在下面的代码中,如果一些数组元素的构造/破坏抛出了什么?

    X* x = new X[10]; // (1)
    delete[] x;       // (2)
    

    我知道可以防止内存泄漏,但除此之外:

    1. Ad(1),之前构造的元素是否被破坏?如果是,如果析构函数在这种情况下抛出,会发生什么?

    2. Ad(2),尚未被破坏的元素是否已被破坏?如果是,如果析构函数再次抛出,会发生什么?

    2 回复  |  直到 7 年前
        1
  •  5
  •   Useless    7 年前
    1. 是的,如果 x[5] x[0]..x[4] 已成功构建的将被正确销毁。

      • 析构函数不应抛出。如果是析构函数 抛出时,会在仍在处理前一个(构造函数)异常时发生这种情况。由于不支持嵌套异常, std::terminate 立即调用。这是 析构函数不应该抛出。
    2. 这里有两个相互排斥的选项:

      1. 如果你 标签 (2) ,构造函数 投也就是说,如果 x

      2. 如果构造函数在步骤中抛出了一部分 (1) 从未真正存在过 完全

    要理解的关键是 x

    如果构造函数失败,该语言不会给你一些不可用的半初始化的东西,因为你无论如何都不能用它做任何事情。(您甚至无法安全地删除它,因为无法跟踪哪些元素是构造的,哪些只是随机垃圾)。

        2
  •  3
  •   Xirema    7 年前

    我们可以使用以下代码进行测试:

    #include <iostream>
    
    //`Basic` was borrowed from some general-purpose code I use for testing various issues 
    //relating to object construction/assignment
    struct Basic {
        Basic() { 
            std::cout << "Default-Constructor" << std::endl; 
            static int val = 0;
            if(val++ == 5) throw std::runtime_error("Oops!");
        }
        Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; }
        Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; }
        Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; }
        Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; }
        ~Basic() noexcept { std::cout << "Destructor" << std::endl; }
    };
    
    int main() {
        Basic * ptrs = new Basic[10];
        delete[] ptrs;
        return 0;
    }
    

    Default-Constructor
    Default-Constructor
    Default-Constructor
    Default-Constructor
    Default-Constructor
    Default-Constructor
    [std::runtime_error thrown and uncaught here]
    

    int main() {
        try {
            Basic * ptrs = new Basic[10];
            delete[] ptrs;
        } catch (std::runtime_error const& e) {std::cerr << e.what() << std::endl;}
        return 0;
    }
    

    输出更改为:

    Default-Constructor
    Default-Constructor
    Default-Constructor
    Default-Constructor
    Default-Constructor
    Default-Constructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Oops!
    

    因此,对于完全构造的对象,即使没有显式的 delete[] new[]

    但是你必须担心第六个问题:在我们的例子中,因为 Basic 做资源管理,如果它的构造函数可以这样抛出),我们不必担心。但如果我们的代码看起来像这样,我们可能不得不担心:

    #include <iostream>
    
    struct Basic {
        Basic() { std::cout << "Default-Constructor" << std::endl; }
        Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; }
        Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; }
        Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; }
        Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; }
        ~Basic() noexcept { std::cout << "Destructor" << std::endl; }
    };
    
    class Wrapper {
        Basic * ptr;
    public:
        Wrapper() : ptr(new Basic) { 
            std::cout << "WRDefault-Constructor" << std::endl;
            static int val = 0;
            if(val++ == 5) throw std::runtime_error("Oops!");
        }
        Wrapper(Wrapper const&) = delete; //Disabling Copy/Move for simplicity
        ~Wrapper() noexcept { delete ptr; std::cout << "WRDestructor" << std::endl; }
    };
    
    int main() {
        try {
            Wrapper * ptrs = new Wrapper[10];
            delete[] ptrs;
        } catch (std::runtime_error const& e) {std::cout << e.what() << std::endl;}
        return 0;
    }
    

    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Oops!
    

    Wrapper 对象不会泄漏内存,但第六个 包装器 对象将泄漏 基本的 对象,因为未正确清理!


    幸运的是,与任何资源管理方案通常的情况一样,如果使用智能指针,所有这些问题都会消失:

    #include <iostream>
    #include<memory>
    
    struct Basic {
        Basic() { std::cout << "Default-Constructor" << std::endl; }
        Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; }
        Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; }
        Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; }
        Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; }
        ~Basic() noexcept { std::cout << "Destructor" << std::endl; }
    };
    
    class Wrapper {
        std::unique_ptr<Basic> ptr;
    public:
        Wrapper() : ptr(new Basic) { 
            std::cout << "WRDefault-Constructor" << std::endl;
            static int val = 0;
            if(val++ == 5) throw std::runtime_error("Oops!");
        }
        //Wrapper(Wrapper const&) = delete; //Copy disabled by default, move enabled by default
        ~Wrapper() noexcept { std::cout << "WRDestructor" << std::endl; }
    };
    
    int main() {
        try {
            std::unique_ptr<Wrapper[]> ptrs{new Wrapper[10]}; //Or std::make_unique
        } catch (std::runtime_error const& e) {std::cout << e.what() << std::endl;}
        return 0;
    }
    

    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Default-Constructor
    WRDefault-Constructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    WRDestructor
    Destructor
    Oops!
    

    请注意 Destructor Default-Constructor ,这告诉我们 基本的 物体现在正在被正确清理。因为资源管理 was DO已委托给 unique_ptr 对象,第六个 对象没有调用其deleter不再是问题。

    现在,这其中很多都涉及到人工代码:没有一个合理的程序员会有一个资源管理器 throw 没有正确的处理代码,即使使用智能指针使其“安全”。但是有些程序员就是不合理,即使他们是合理的,你也可能会遇到一个奇怪的、奇异的场景,你必须这样编写代码。那么,就我而言,教训就是始终使用智能指针和其他STL对象来管理动态内存。不要试着自己滚。当你试着调试东西时,它会帮你省去像这样的头痛。