如何知道在C++中何时调用delete和delete[]?

3

我正在实现类my_unique_ptrmy_shared_ptr,它们模拟了标准库的智能指针std::unique_ptrstd::shared_ptr,以便更好地了解它们。

当我实现析构函数时,我在纠结是使用delete还是delete[]来释放内存。尽管我在SO和其他网站上阅读了很多内容,但没有一种便携式的方法可以知道由new[]分配了多少字节(或者是否使用了newnew[])(How many bytes were allocated by new?)。

在实现类my_unique_ptr时,我无法知道用户将从构造函数请求多少字节,即:

他会做 my_unique_ptr<int> ptr1(new int)

或者他会做my_unique_ptr<int> ptr1(new int[5])

如果有办法,请让我知道!

这是我的类(简化并省略了复制/移动构造函数):

template<typename ValueType>
class my_unique_ptr {

    ValueType *internal_ptr = nullptr;

public:
    
    // Default constructor
    my_unique_ptr() {
       internal_ptr = nullptr;
    }

    // Paramterized constructor
    explicit my_unique_ptr(ValueType *ptr) {
       if (ptr)
        internal_ptr = ptr;
    }

    // Destructor
    ~my_unique_ptr() {
       
        // How do I know whether to use
           delete ptr;

        // or
           delete[] ptr;
        
     }

我曾在某处读到,编译器会跟踪new[]的参数,然后由delete[]正确释放内存。我还读到过:“程序员应该负责匹配newdelete,以及new[]delete[]”。但是,我该如何在我的类中编写代码,以使其始终匹配正确的操作符呢?

附注:

在我的构造函数中,使用 delete 而不是delete[],同时传递超过1个 int(例如使用 new int[5]),使用 valgrind 运行时,Valgrind会显示所有内存块都被释放了,没有泄漏的机会! (尽管显示警告,例如 mismatched new[] with delete)。这是否意味着delete成功释放了由new[]分配的所有5个int? 请帮忙解答。 我使用的操作系统是Ubuntu 18.04,使用的编译器是gcc 7.5.0


1
最简单的方法是专门化 my_unique_ptr<T[]>,然后要求用户使用 my_unique_ptr<int[]> ptr1(new int[5]);。这就是标准库智能指针所做的。 - cdhowie
@cdhowie 我现在明白了,谢谢! - Siraj Qazi
@cdhowie 很有趣,当你用回复摧毁他们的时候,这里的每个人都在删除他们的答案 xD - Siraj Qazi
@SirajQazi 哈哈,我也收到了类似的回复,但是我的解决方案也是有效的!所以我不会删除那个.. - K.R.Park
@K.R.Park 哈哈,当然了,还要感谢您回答我的问题 :) - Siraj Qazi
@SirajQazi 实际上,我没有回复任何已删除的答案。 :) - cdhowie
2个回答

2

您说得对,您无法知道内存是由new还是new[]分配的。但是您甚至无法知道内存是否存储了自动持续时间对象。例如,您的用户可以执行以下操作:

int a = 24;

my_unique_ptr<int> ptr1(&a); // oups

你无法知道这个。所以要做的就是像标准库一样:

  • 为数组类型制作一个专门化版本,例如template <class T> class my_unique_ptr<T[]>,在析构函数中调用delete[]

  • my_unique_ptr<T>必须使用从new获取的内存进行初始化,my_unique_ptr<T[]>必须使用从new[]获取的内存进行初始化。这是您的库用户必须遵守的合同。如果没有遵守,则会出现未定义行为。

  • 通过提供类似于std::make_unique的等效物来帮助用户遵守此合同。还请参见使用std :: make_unique而不是new运算符的优点


这是否意味着delete成功释放了由new[]分配的所有5个整数?

不是的。调用delete释放不是从new获得的内存会导致未定义行为。


我明白了。非常感谢您的解释!我想我没有考虑到可能会有另一个使用<T[]>的模板特化,所以我一直无法理解编译器如何正确匹配运算符。再次感谢您! :) - Siraj Qazi

0

实际上,标准库的智能指针比你的代码多了一个功能,它们能够在析构函数被调用时选择使用哪个操作符(delete或delete[])。它们默认使用delete,如果你想使用delete[],则需要在智能指针的构造函数中提供它。除此之外,如果你创建单个对象,请使用new和delete;如果你创建数组,请使用new[],并使用delete[]来删除它。


4
你完全不需要使用自定义的删除器,只需使用例如 std::unique_ptr<int[]>。对于 T[],有一个特化版本会使用 delete[] - cdhowie
@cdhowie 我完全不知道这种解决方案。学到了一个好点子。谢谢。 - K.R.Park
是的,这些都没问题,但我不知道用户是要创建一个单独的对象还是一个数组,这就是我问的问题。无论如何,我已经从@bolov和cdhowie那里得到了答案。 - Siraj Qazi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接