提问人:Thore Haupt 提问时间:9/27/2023 最后编辑:user207421Thore Haupt 更新时间:11/3/2023 访问量:163
这些析构函数调用中哪一个被多次执行?
Which of these destructor calls are excecuted multiple times?
问:
我现在正在学习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)
所以我猜,我的析构函数是不正确的。在我的脑海中,我在某个地方尝试删除之前已被其他析构函数删除的对象。在我删除第一个包含指针引用的指针时,我将指针设置为 ,我命令析构函数不要删除顶部指针数组。也许这不起作用,因为结构内部的指针与常规结构中的指针相同。rows
bits
nullptr
bits
rows
BitArray2
我希望获得有关问题的更多信息,或者我可以研究一些来源以了解我遇到的问题。
答:
按照原始帖子中的定义进行修补BitArray2
大多数问题都在评论中被发现:
- 委托构造函数需要成为基成员初始化的一部分。
- 班级需要使用“三法则”或“五法则”。
BitArray2
- 不必要的复杂功能。
main
- 错误地使用了析构函数而不是 。
delete[]
delete
下面的代码中标记了修复程序,并附有提供其他详细信息的注释。查找。#if FIX_BY_TBXFREEWARE == 1
我还发现了一些其他需要解决的问题:
- 下标运算符在为零时将失败。通过添加签入构造函数进行修复。
size
- 微妙的内存泄漏是可能的。请参阅我在构造函数中的注释。
- 每当我编码时,我都会自动编写一个版本来配合它。我在这里做到了。
operator=
const
- 结构不需要析构函数。它不使用运算符 。因此,它不应使用运算符。它是一个“零规则”结构。
Row
new
delete
@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
评论
从头开始写作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 选择复制构造函数。
这种实现不需要数组是正方形的。我添加了一个新的构造函数 ,以便您可以拥有具有不同行数和列数的数组。它与复制构造函数共享函数。这就是所有乐趣发生的地方。BitArray2
BitArray2(n_rows, n_cols)
allocate_memory
为了使该类更通用,我引入了类型别名作为 的同义词,并将成员变量重命名为 。如果将来要有一个不同值类型的数组,您所要做的就是更改 的定义。value_type
bool
bits
data_ptr
value_type
同时,我将成员变量的名称更改为 。该名称反映了 的名称。两者都是指向指针的指针。rows
row_ptr
data_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_{};
下标不再有类型;现在他们使用 ,这是 的别名。int
size_type
std::size_t
一对 “getter” 函数返回 .BitArray2
size_type n_rows() const noexcept
{
return n_rows_;
}
size_type n_cols() const noexcept
{
return n_cols_;
}
内存分配
每次使用 operator 时,内存分配都有可能失败。发生这种情况时,Operator 会抛出一个对象。如果抛出发生在构造函数内部,这可能会很麻烦。new
new
std::bad_alloc
在对象完全构造之后(即在其构造函数完成运行之后),才能调用对象的析构函数。因此,如果构造函数引发异常,则通常由析构函数执行的操作将不会运行。您必须在构造函数中手动执行它们。delete
这意味着构造函数中对运算符的每次调用(除了第一个调用)都必须包含在 try/catch 块中。当抛出 a 时,您必须捕获它,并手动解除分配,即删除在抛出异常之前所做的任何分配。考虑到 的设计,可能会有很多!new
std::bad_alloc
BitArray2
Function 使用几个辅助函数 和 来处理删除。allocate_memory
deallocate_data_ptr
deallocate_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_memory
std::bad_alloc
row
n_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
下标是原始类最优雅的功能之一。巧妙地使用类来提供第二个函数(用于列)是我以前从未见过的。BitArray2
Row
operator[]
在 的这个实现中,几乎没有什么变化。最大的区别是它不再通过返回数组的第一个元素来解决下标错误。相反,它会引发异常。BitArray2
std::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
评论
std::vector
BitArray2
(j+i)%2 == 0?true:false
(j+i)%2 == 0
if
std::cout << b[i][j]
std::endl
'\n'
destructor
deconstructor
this
new
delete