这些析构函数调用中哪一个被多次执行?

Which of these destructor calls are excecuted multiple times?

提问人:Thore Haupt 提问时间:9/27/2023 最后编辑:user207421Thore Haupt 更新时间:11/3/2023 访问量:163

问:

我现在正在学习C++,来自Java。我确实知道我应该使用 s 而不是数组,但是我还想了解基础知识。vector

我写了一个结构,我想用double .这确实有效,并且实现了它的目的。但是,当调用析构函数时,我收到一个错误:[]

free(): invalid pointer
Aborted (core dumped)

我问了一个朋友,他也帮不了我。

结构如下:

#pragma once
class BitArray2
{
public:

    int size;
    
    BitArray2();
    BitArray2(int size);

    ~BitArray2();

    bool** bits = nullptr;

    struct Row{
        public:
        int size;
        bool** bits_row = nullptr;
        int index;

        Row(int size, int index, bool** bits_row)
            :size(size), index(index), bits_row(bits_row){}


        ~Row(){
            bits_row = nullptr;
        }

        bool& operator[](int col_index);
    };

    Row** rows = nullptr;

    Row& operator[](int row_index);



};

BitArray2::BitArray2(){
    BitArray2(1);
}

BitArray2::BitArray2(int size)
    :size(size){
    bits = new bool*[size];
    rows = new Row*[size];
    for (int i = 0; i < size; i++)
    {
        rows[i] = new Row(size, i, bits);
        bits[i] = new bool[size];
    }
    
}

BitArray2::~BitArray2(){
    for (int i = 0; i < size; i++)
    {
        delete[] rows[i];
    }
    for (int i = 0; i < size; i++)
    {
        delete[] bits[i];
    }
    delete[] rows;
    delete[] bits;
}

bool& BitArray2::Row::operator[](const int col_index)
{
    if(col_index < size){
        return bits_row[index][col_index];
    }
    else{
        return bits_row[0][0];
    }
}

BitArray2::Row& BitArray2::operator[](int row_index)
{
    if(row_index < size){
        return  *rows[row_index];
    }
    else{
        return *rows[0];
    }
}

我还将给你一个示例应用程序:

BitArray2 b(4);
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            b[i][j] = (j+i)%2 == 0?true:false;
        }
    }
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            if(b[i][j]){
                std::cout << 1;
            }else{
                std::cout << 0;
            }
        }
        std::cout << std::endl;
    }

输出:

1010
0101
1010
0101
free(): invalid pointer
Aborted (core dumped)

所以我猜,我的析构函数是不正确的。在我的脑海中,我在某个地方尝试删除之前已被其他析构函数删除的对象。在我删除第一个包含指针引用的指针时,我将指针设置为 ,我命令析构函数不要删除顶部指针数组。也许这不起作用,因为结构内部的指针与常规结构中的指针相同。rowsbitsnullptrbitsrowsBitArray2

我希望获得有关问题的更多信息,或者我可以研究一些来源以了解我遇到的问题。

C++ 析构函数

评论

1赞 Some programmer dude 9/27/2023
除非是作业的要求,否则不要使用指针和显式动态分配。对于动态数组,请使用 .std::vector
4赞 Some programmer dude 9/27/2023
而且你不遵循三、五或零的规则。如果您按值传递或返回对象,则需要这样做。BitArray2
1赞 Pete Becker 9/27/2023
这并不能解决问题,但比它需要的要详细得多。 做完全一样的事情。此外,不需要显示值的语句; 做同样的事情。最后,这段代码不需要额外的东西。 结束一行。(j+i)%2 == 0?true:false(j+i)%2 == 0ifstd::cout << b[i][j]std::endl'\n'
2赞 PaulMcKenzie 9/27/2023
在我的脑海中,我试图删除一个之前已经被其他转换器删除的对象——首先,术语是 ,而不是 。其次,您应该在析构函数中打印 的值,而不是猜测要删除哪个对象。最后,您应该使用工作示例作为指导,而不是试图通过反复试验来学习 C++。您的代码有太多错误。如果你坚持不使用向量,这里有一个二维数组类destructordeconstructorthis
2赞 aschepler 9/27/2023
在这一点上,和运算符的使用是高级的 C++,而不是基础。理解它们对于维护旧代码和与需要它们的旧代码进行交互很有用,但否则它们几乎永远不会出现在全新的代码中。newdelete

答:

0赞 tbxfreeware 9/27/2023 #1

按照原始帖子中的定义进行修补BitArray2

大多数问题都在评论中被发现:

  1. 委托构造函数需要成为基成员初始化的一部分。
  2. 班级需要使用“三法则”或“五法则”。BitArray2
  3. 不必要的复杂功能。main
  4. 错误地使用了析构函数而不是 。delete[]delete

下面的代码中标记了修复程序,并附有提供其他详细信息的注释。查找。#if FIX_BY_TBXFREEWARE == 1

我还发现了一些其他需要解决的问题:

  1. 下标运算符在为零时将失败。通过添加签入构造函数进行修复。size
  2. 微妙的内存泄漏是可能的。请参阅我在构造函数中的注释。
  3. 每当我编码时,我都会自动编写一个版本来配合它。我在这里做到了。operator=const
  4. 结构不需要析构函数。它不使用运算符 。因此,它不应使用运算符。它是一个“零规则”结构。Rownewdelete

@PaulMcKenzie链接到一个出色的示例,该示例演示了如何正确编码动态分配的二维数组。他的代码值得研究。

// BitArray2.h
#pragma once
#define FIX_BY_TBXFREEWARE 1
class BitArray2
{
public:

    int size;

    BitArray2();
    BitArray2(int size);

    ~BitArray2();

#if FIX_BY_TBXFREEWARE == 1
    // Class BitArray2 follows the "Rule of Three", 
    // or, possibly, the "Rule of Five". Either way, 
    // I have deleted the copy constructor and 
    // copy-assignment operator, so that they cannot 
    // be invoked. If you need them, you will need to 
    // code "deep-copy" versions of each.
    BitArray2(BitArray2 const&) = delete;
    BitArray2& operator=(BitArray2 const&) = delete;
#endif

    bool** bits = nullptr;

    struct Row {
    public:
        int size;
        bool** bits_row = nullptr;
        int index;

        Row(int size, int index, bool** bits_row)
            : size(size), index(index), bits_row(bits_row)
        {}

#if FIX_BY_TBXFREEWARE == 1
        // Struct Row follows the "Rule of Zero".
        // Nothing was allocated with operator new,
        // so nothing gets deleted. Under the "Rule 
        // of Zero" there is no need for a destructor.
#else
        ~Row() {
            bits_row = nullptr;
        }
#endif

        bool& operator[](int col_index);
#if FIX_BY_TBXFREEWARE == 1
        // Support for constant objects
        bool const& operator[](int col_index) const;
#endif
    };

    Row** rows = nullptr;

    Row& operator[](int row_index);
#if FIX_BY_TBXFREEWARE == 1
    // Support for constant objects
    Row const& operator[](int row_index) const;
#endif

};
// end file: BitArray2.h
// BitArray2.cpp
#include <stdexcept>
#include "BitArray2.h"

#if FIX_BY_TBXFREEWARE == 1
// Fix delegating constructor.
BitArray2::BitArray2() 
    : BitArray2(1)
{}
#else
BitArray2::BitArray2() {
    BitArray2(1);
}
#endif

BitArray2::BitArray2(int size)
    : size(size)
{
#if FIX_BY_TBXFREEWARE == 1
    // The implementation of operator[] requires a non-zero size.
    if (size < 1)
        throw std::invalid_argument("Invalid size");

    // Possible memory leak
    // If the ctor fails, and throws std::bad_alloc, 
    // the dtor will NOT be called. When that happens, 
    // any allocations that DID WORK need to be deleted 
    // here. I have not coded a fix for this.
#endif

    bits = new bool* [size];
    rows = new Row * [size];
    for (int i = 0; i < size; i++)
    {
        rows[i] = new Row(size, i, bits);
        bits[i] = new bool[size];
    }
}

BitArray2::~BitArray2() {
#if FIX_BY_TBXFREEWARE == 1
    // Changed `delete[]` to `delete`.
    for (int i = 0; i < size; i++)
    {
        delete rows[i];
        delete[] bits[i];
    }
#else
    for (int i = 0; i < size; i++)
    {
        delete[] rows[i];
    }
    for (int i = 0; i < size; i++)
    {
        delete[] bits[i];
    }
#endif
    delete[] rows;
    delete[] bits;
}

bool& BitArray2::Row::operator[](const int col_index)
{
    if (col_index < size) {
        return bits_row[index][col_index];
    }
    else {
        return bits_row[0][0];
    }
}
#if FIX_BY_TBXFREEWARE == 1
// Support for constant objects
bool const&
BitArray2::Row::operator[](const int col_index) const
{
    if (col_index < size) {
        return bits_row[index][col_index];
    }
    else {
        return bits_row[0][0];
    }
}
#endif
BitArray2::Row& BitArray2::operator[](int row_index)
{
    if (row_index < size) {
        return *rows[row_index];
    }
    else {
        return *rows[0];
    }
}
#if FIX_BY_TBXFREEWARE == 1
// Support for constant objects
BitArray2::Row const&
BitArray2::operator[](int row_index) const
{
    if (row_index < size) {
        return *rows[row_index];
    }
    else {
        return *rows[0];
    }
}
#endif
// end file: BitArray2.cpp
// main.cpp
#include <iostream>
#include "BitArray2.h"
int main()
{
    BitArray2 b(4);
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
            b[i][j] = (j + i) % 2 == 0 ? true : false;

    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
            if (b[i][j])
                std::cout << 1;
            else
                std::cout << 0;
        std::cout << std::endl;
    }
}
// end file: main.cpp

输出:

1010
0101
1010
0101

评论

0赞 Thore Haupt 9/27/2023
谢谢!这太棒了,我带走了很多。我很感激。
0赞 tbxfreeware 11/2/2023 #2

从头开始写作BitArray2

下面的类可能是你开始写作时所想象的。BitArray2

它遵循“4法则(半法则)”,这是Arthur O'Dwyer在2019年CppCon演讲中使用的名称。你得到“一半”,因为复制赋值运算符和移动赋值运算符已组合在同一函数中。

        BitArray2& operator= (BitArray2 that) noexcept
        {
            swap(that);
            return *this;
        }
        void swap(BitArray2& that) noexcept
        {
            using std::swap;
            swap(this->data_ptr, that.data_ptr);
            swap(this->row_ptr, that.row_ptr);
            swap(this->n_rows_, that.n_rows_);
            swap(this->n_cols_, that.n_cols_);
        }
        friend void swap(BitArray2& a, BitArray2& b) noexcept
        {
            a.swap(b);
        }

这就是复制和交换算法。当编译器将参数复制到 value 参数中时,它将为 rvalue 参数选择 move 构造函数,为 lvalues 选择复制构造函数。

这种实现不需要数组是正方形的。我添加了一个新的构造函数 ,以便您可以拥有具有不同行数和列数的数组。它与复制构造函数共享函数。这就是所有乐趣发生的地方。BitArray2BitArray2(n_rows, n_cols)allocate_memory

为了使该类更通用,我引入了类型别名作为 的同义词,并将成员变量重命名为 。如果将来要有一个不同值类型的数组,您所要做的就是更改 的定义。value_typeboolbitsdata_ptrvalue_type

同时,我将成员变量的名称更改为 。该名称反映了 的名称。两者都是指向指针的指针。rowsrow_ptrdata_ptr

    public:
        using value_type = bool;
        using size_type = std::size_t;

    private:
        // Instance variables
        value_type** data_ptr{ nullptr };
        Row** row_ptr{ nullptr };
        size_type n_rows_{}, n_cols_{};

下标不再有类型;现在他们使用 ,这是 的别名。intsize_typestd::size_t

一对 “getter” 函数返回 .BitArray2

        size_type n_rows() const noexcept
        {
            return n_rows_;
        }
        size_type n_cols() const noexcept
        {
            return n_cols_;
        }

内存分配

每次使用 operator 时,内存分配都有可能失败。发生这种情况时,Operator 会抛出一个对象。如果抛出发生在构造函数内部,这可能会很麻烦。newnewstd::bad_alloc

在对象完全构造之后(即在其构造函数完成运行之后),才能调用对象的析构函数。因此,如果构造函数引发异常,则通常由析构函数执行的操作将不会运行。您必须在构造函数中手动执行它们。delete

这意味着构造函数中对运算符的每次调用(除了第一个调用)都必须包含在 try/catch 块中。当抛出 a 时,您必须捕获它,并手动解除分配,即删除在抛出异常之前所做的任何分配。考虑到 的设计,可能会有很多!newstd::bad_allocBitArray2

Function 使用几个辅助函数 和 来处理删除。allocate_memorydeallocate_data_ptrdeallocate_row_ptr

        void deallocate_data_ptr(size_type row) noexcept
        {
            while (row--)
                delete[] data_ptr[row];
            delete data_ptr;
            data_ptr = nullptr;
        }
        void deallocate_row_ptr(size_type row) noexcept
        {
            while (row--)
                delete row_ptr[row];
            delete row_ptr;
            row_ptr = nullptr;
        }

这些帮助程序函数执行双重任务,因为它们也是从析构函数调用的。

        ~BitArray2() noexcept
        {
            if (row_ptr)
                deallocate_row_ptr(n_rows_);
            if (data_ptr)
                deallocate_data_ptr(n_rows_);
        }

当从析构函数调用时,帮助程序函数会删除所有行(因为参数是 ),但是当从 调用它们时,它们只会删除触发 .因此,它们被用参数调用,而不是 。n_rows_allocate_memorystd::bad_allocrown_rows_

这是功能:allocate_memory

        void allocate_memory()
        {
            // The first call to operator `new` is free. If it throws 
            // a `std::bad_alloc`, we do not need to catch it. Since 
            // nothing will have been allocated, there is nothing that 
            // needs to be deallocated.
            data_ptr = new value_type* [n_rows_];

            // Every subsequent call to `new` must occur in a try/catch 
            // block. If `new` throws a `std::bad_alloc`, we must catch 
            // it, and deallocate all previously allocated memory.
            for (size_type row{}; row < n_rows_; ++row)
            {
                try { data_ptr[row] = new value_type[n_cols_]{}; }
                catch (std::bad_alloc const&) { deallocate_data_ptr(row); throw; }
            }

            try { row_ptr = new Row* [n_rows_]; }
            catch (std::bad_alloc const&) { deallocate_data_ptr(n_rows_); throw; }

            for (size_type row{}; row < n_rows_; ++row)
            {
                try { row_ptr[row] = new Row{ n_cols_, row, data_ptr }; }
                catch (std::bad_alloc const&)
                {
                    deallocate_row_ptr(row);
                    deallocate_data_ptr(n_rows_);
                    throw;
                }
            }
        }

函数是从主构造函数以及复制构造函数调用的。在主构造函数中,我现在允许具有零行和零列的数组,但前提是两者都为零。当然,你对一个空数组无能为力,但你可以通过从另一个数组赋值来替换这样的数组。allocate_memory

        explicit BitArray2(
            size_type const n_rows,
            size_type const n_cols)
            : data_ptr{ nullptr }
            , row_ptr{ nullptr }
            , n_rows_{ n_rows }
            , n_cols_{ n_cols }
        {
            if (n_rows && n_cols)
                allocate_memory();
            else if (!n_rows && n_cols)
                throw std::invalid_argument("BitArray2: number of rows is 0");
            else if (n_rows && !n_cols)
                throw std::invalid_argument("BitArray2: number of columns is 0");
        }

        BitArray2(BitArray2 const& that)
            : data_ptr{ nullptr }
            , row_ptr{ nullptr }
            , n_rows_{ that.n_rows_ }
            , n_cols_{ that.n_cols_ }
        {
            if (that.data_ptr)
            {
                allocate_memory();
                for (size_type row{}; row < n_rows_; ++row)
                    std::copy(
                        &that.data_ptr[row][zero],
                        &that.data_ptr[row][n_cols_],
                        &this->data_ptr[row][zero]);
            }
        }

使用 和 类下标operator[]Row

下标是原始类最优雅的功能之一。巧妙地使用类来提供第二个函数(用于列)是我以前从未见过的。BitArray2Rowoperator[]

在 的这个实现中,几乎没有什么变化。最大的区别是它不再通过返回数组的第一个元素来解决下标错误。相反,它会引发异常。BitArray2std::out_of_range

    public:
        Row& operator[](size_type const row_index)
        {
            check_row_subscript(row_index);
            return *row_ptr[row_index];
        }
        Row const& operator[](size_type const row_index) const
        {
            check_row_subscript(row_index);
            return *row_ptr[row_index];
        }

    private:
        void check_row_subscript(const size_type row) const
        {
            if (n_rows_ <= row)
                throw std::out_of_range("BitArray2: Row index is out of range.");
        }

此外,我将结构更改为适当的类。Row

        class Row
        {
            // Class `Row` adds a bit of syntatic sugar.
            // Its `operator[]` will become the second subscript 
            // for a `BitArray2` object, i.e, it will be the column
            // subscript.
            size_type n_cols_in_row{};         // a copy of `n_cols_`
            size_type index{};                 // row subscript of `this` row
            value_type** data_row{ nullptr };  // a copy of `data_ptr`
        public:
            Row(
                size_type const n_cols_in_row,
                size_type const index,
                value_type** const data_row)
                : n_cols_in_row{ n_cols_in_row }
                , index{ index }
                , data_row{ data_row }
            {}
            value_type& operator[] (size_type const col_index)
            {
                check_column_subscript(col_index);
                return data_row[index][col_index];
            }
            value_type const& operator[] (size_type const col_index) const
            {
                check_column_subscript(col_index);
                return data_row[index][col_index];
            }
        private:
            void check_column_subscript(size_type const col) const
            {
                if (n_cols_in_row <= col)
                    throw std::out_of_range("BitArray2: Column index is out of range.");
            }
        };

完整的班级BitArray2

除了 move 构造函数和另外两个构造函数之外,我们唯一还没有看到的函数是相等运算符。您将在下面的完整源代码中找到它以及其他所有内容。

此实现可以轻松处理原始帖子中的所有测试用例。

// BitArray2.h
#ifndef BITARRAY2_H
#define BITARRAY2_H

#include <algorithm>  // copy, equal, swap
#include <cstddef>    // size_t
#include <new>        // bad_alloc
#include <stdexcept>  // invalid_argument, out_of_range
#include <utility>    // exchange

class BitArray2
{
    // The `BitArray2` class in the OP is a square, two-
    // dimensional array of `bools`. The number of rows and 
    // the number of columns are both equal to argument `size` 
    // in the constructor.
    // 
    // In this implementation, `BitArray2` is a rectangular 
    // array, where the number rows, `n_rows_`, does not have 
    // to be the same as the number of columns, `n_cols_`.
    // 
    // Rather than hard-code type `bool` into the code, we use 
    // type alias `value_type`, which is an alias for `bool`. 
    // 
    // It the future, it should be relatively easy to change 
    // `BitArray2` into a class template that handles other 
    // value types.
public:
    using value_type = bool;
    using size_type = std::size_t;

private:
    class Row
    {
        // Class `Row` adds a bit of syntatic sugar.
        // Its `operator[]` will become the second subscript 
        // for a `BitArray2` object, i.e, it will be the column
        // subscript.
        size_type n_cols_in_row{};         // a copy of `n_cols_`
        size_type index{};                 // row subscript of `this` row
        value_type** data_row{ nullptr };  // a copy of `data_ptr`
    public:
        Row(
            size_type const n_cols_in_row,
            size_type const index,
            value_type** const data_row)
            : n_cols_in_row{ n_cols_in_row }
            , index{ index }
            , data_row{ data_row }
        {}
        value_type& operator[] (size_type const col_index)
        {
            check_column_subscript(col_index);
            return data_row[index][col_index];
        }
        value_type const& operator[] (size_type const col_index) const
        {
            check_column_subscript(col_index);
            return data_row[index][col_index];
        }
    private:
        void check_column_subscript(size_type const col) const
        {
            if (n_cols_in_row <= col)
                throw std::out_of_range("BitArray2: Column index is out of range.");
        }
    };

    // Instance variables
    value_type** data_ptr{ nullptr };
    Row** row_ptr{ nullptr };
    size_type n_rows_{}, n_cols_{};
    enum : size_type { zero, one };

public:
    // Constructors
    BitArray2()
        : BitArray2{ one, one }
    {}
    explicit BitArray2(size_type const size)
        : BitArray2{ size, size }
    {}
    explicit BitArray2(
        size_type const n_rows,
        size_type const n_cols)
        : data_ptr{ nullptr }
        , row_ptr{ nullptr }
        , n_rows_{ n_rows }
        , n_cols_{ n_cols }
    {
        if (n_rows && n_cols)
            allocate_memory();
        else if (!n_rows && n_cols)
            throw std::invalid_argument("BitArray2: number of rows is 0");
        else if (n_rows && !n_cols)
            throw std::invalid_argument("BitArray2: number of columns is 0");
    }

    // "Rule of Four (and a half)" (plus a noexcept swap function)
    //   1. destructor
    //   2. copy constructor
    //   3. move constructor
    //   4. copy-assignment operator
    //   4.5. move-assignment operator (handled by copy-and-swap assignment)
    //   5. `swap` used to implement assignment operators

    ~BitArray2() noexcept
    {
        if (row_ptr)
            deallocate_row_ptr(n_rows_);
        if (data_ptr)
            deallocate_data_ptr(n_rows_);
    }
    BitArray2(BitArray2 const& that)
        : data_ptr{ nullptr }
        , row_ptr{ nullptr }
        , n_rows_{ that.n_rows_ }
        , n_cols_{ that.n_cols_ }
    {
        if (that.data_ptr)
        {
            allocate_memory();
            for (size_type row{}; row < n_rows_; ++row)
                std::copy(
                    &that.data_ptr[row][zero],
                    &that.data_ptr[row][n_cols_],
                    &this->data_ptr[row][zero]);
        }
    }
    BitArray2(BitArray2&& that) noexcept
        : data_ptr{ std::exchange(that.data_ptr, nullptr) }
        , row_ptr{ std::exchange(that.row_ptr, nullptr) }
        , n_rows_{ std::exchange(that.n_rows_, zero) }
        , n_cols_{ std::exchange(that.n_cols_, zero) }
    {}
    BitArray2& operator= (BitArray2 that) noexcept
    {
        swap(that);
        return *this;
    }
    void swap(BitArray2& that) noexcept
    {
        using std::swap;
        swap(this->data_ptr, that.data_ptr);
        swap(this->row_ptr, that.row_ptr);
        swap(this->n_rows_, that.n_rows_);
        swap(this->n_cols_, that.n_cols_);
    }
    friend void swap(BitArray2& a, BitArray2& b) noexcept
    {
        a.swap(b);
    }

    // Subscripting operators
    Row& operator[](size_type const row_index)
    {
        check_row_subscript(row_index);
        return *row_ptr[row_index];
    }
    Row const& operator[](size_type const row_index) const
    {
        check_row_subscript(row_index);
        return *row_ptr[row_index];
    }

    // "Getter" functions
    size_type n_rows() const noexcept
    {
        return n_rows_;
    }
    size_type n_cols() const noexcept
    {
        return n_cols_;
    }

    // Equality operator
    bool operator== (BitArray2 const& that) const
    {
        if (this->n_rows_ != that.n_rows_ ||
            this->n_cols_ != that.n_cols_)
            return false;
        for (size_type row{}; row < this->n_rows_; ++row)
            if (!std::equal(
                &this->data_ptr[row][zero],
                &this->data_ptr[row][this->n_cols_],
                &that.data_ptr[row][zero])
                )
                return false;
        return true;
    }
private:
    void allocate_memory()
    {
        // The first call to operator `new` is free. If it throws 
        // a `std::bad_alloc`, we do not need to catch it. Since 
        // nothing will have been allocated, there is nothing that 
        // needs to be deallocated.
        data_ptr = new value_type* [n_rows_];

        // Every subsequent call to `new` must occur in a try/catch 
        // block. If `new` throws a `std::bad_alloc`, we must catch 
        // it, and deallocate all previously allocated memory.
        for (size_type row{}; row < n_rows_; ++row)
        {
            try { data_ptr[row] = new value_type[n_cols_]{}; }
            catch (std::bad_alloc const&) { deallocate_data_ptr(row); throw; }
        }

        try { row_ptr = new Row* [n_rows_]; }
        catch (std::bad_alloc const&) { deallocate_data_ptr(n_rows_); throw; }

        for (size_type row{}; row < n_rows_; ++row)
        {
            try { row_ptr[row] = new Row{ n_cols_, row, data_ptr }; }
            catch (std::bad_alloc const&)
            {
                deallocate_row_ptr(row);
                deallocate_data_ptr(n_rows_);
                throw;
            }
        }
    }
    void check_row_subscript(const size_type row) const
    {
        if (n_rows_ <= row)
            throw std::out_of_range("BitArray2: Row index is out of range.");
    }
    void deallocate_data_ptr(size_type row) noexcept
    {
        while (row--)
            delete[] data_ptr[row];
        delete data_ptr;
        data_ptr = nullptr;
    }
    void deallocate_row_ptr(size_type row) noexcept
    {
        while (row--)
            delete row_ptr[row];
        delete row_ptr;
        row_ptr = nullptr;
    }
};
#endif  // !BITARRAY2_H
// end file: BitArray2.h