C++在分配器中销毁元素时是否会导致双重释放< string>?

e4eetjau  于 2022-11-19  发布在  其他
关注(0)|答案(3)|浏览(213)

C++分配器。我知道字符串将在内部实现中分配一个new的块缓冲区,并在析构函数中释放它(调用delete[])。
我的问题是,当使用allocator<string>时,它是否会免费加倍?
1.字符串析构函数第一次释放
1.指向字符串第二个释放缓冲区已被释放
另外,string和allocate(n)的缓冲区地址是否有相同的区域?

#include <iostream>
#include <memory>
#include <string>

using namespace std;

int main(int argc, char **argv)
{
    const int cnt = 10;
    allocator<string> alloc;
    auto p = alloc.allocate(cnt);

    alloc.construct(p);
    for (int i = 0; i < cnt; ++i)
    {
        cout << p+i << endl; // print buffer address
    }

    alloc.destroy(p); // will it free buffer of string?

    alloc.deallocate(p, cnt); // will it free buffer of string again?

    return 0;
}
ugmeyewa

ugmeyewa1#

让我们从delete开始
当你delete一个对象时,会发生两件事:
1.调用该对象的析构函数
1.为具有new的此对象分配的内存将释放回堆

placement newdelete

可以使用placement new语法在现有内存缓冲区上构造对象:

const char* charString = "Hello, World";
// allocate the required memory
void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
// construct a "Buffer" object on an existing memory block
Buffer* buf = new(mem) Buffer(strlen(charString));

// ...

// destruct the "Buffer" object without releasing the memory
buf->~Buffer();
// deallocate the memory
::operator delete(mem);

当然,对于这个例子,你可以只使用普通的newdelete,但它展示了你如何将内存分配与其构造以及销毁与解除分配分开。
例如,如果您管理内存池,并且在析构对象时可以将内存回收回内存池,而不是回收回堆,则此技术非常有用。

std::分配器

std::allocator提供了上述行为-使用allocateconstructdestroydeallocate方法将内存分配和释放与对象构造和析构分离。

allocator<string> alloc;
size_t n = 3;
auto p = alloc.allocate(n); // memory is allocated for n elements
alloc.construct(p);           // object is constructed on that memory

// ...

alloc.destroy(p);         // object is destructed
alloc.deallocate(p, n); // memory is deallocated

请注意,当析构string对象时,std::string析构函数将在其内部分配上调用delete,但在上述代码中,对象本身占用的内存仅在调用alloc.deallocate时释放。

不建议在C++17中使用constructdestroy

C17声明了std::allocator的方法constructdestroy不推荐使用,而C20则将它们废弃。因此,从C17开始,您要么返回到placement new,或者更好(感谢@Evg的评论)-使用std::allocator_traits::construct(),它的优点是自C20起就是constexpr。对于析构,在使用分配器释放内存后直接调用析构函数:

allocator<string> alloc;
size_t n = 3;
auto p = alloc.allocate(n); // memory is allocated for n elements
// alloc.construct(p);        // deprecated in C++17, obsolete in C++20
// option a:
   // new(p) string; // construct the object with placement new
// option b, better - (potentially) constexpr since C++20:
std::allocator_traits<allocator<string>>::construct(alloc, p, "hello");

// ...

// alloc.destroy(p);      // deprecated in C++17, obsolete in C++20
p->~string(); // destruct the object by calling the destructor
alloc.deallocate(p, n); // memory is deallocated

Code link


  • 分别从C20和C17开始。

对于construct_atdestroy_at,上面的代码可以写为:

allocator<string> alloc;
size_t n = 3;
auto p = alloc.allocate(n); // memory is allocated for n elements
std::construct_at(p, "hello"); // added in C++20

// ...

std::destroy_at(p); // destruct the object - added in C++17
alloc.deallocate(p, n); // memory is deallocated

这是最简单的一个,如果你是在C20我会推荐这个选项。
Code link
最后注意:还有一个叫做placement delete的东西,它不是由程序员直接调用的,而是只有在对象的构造函数抛出异常时才从placement new中调用。所以不要被它搞混了。在不释放对象本身所占用的内存的情况下就地析构对象的方法是直接调用它的析构函数,如上所示(或者如果你在C
17之前使用std::allocator,通过调用allocator destroy方法)。
最后一点:如果您想知道为什么deallocate需要大小,请参阅:why does std::allocator::deallocate require a size?

gwo2fgha

gwo2fgha2#

当你这样写的时候:

Foo *foo = new Foo();

会发生两件事:
1.为Foo对象分配了一些堆空间。
1.调用Foo()构造函数,this指向新分配的空间。
稍后,删除Foo对象:

delete foo;

还有两件事会发生
1.调用析构函数~Foo()
1.为Foo示例分配的内存将释放回堆。
std::allocator类只允许您手动执行这四个步骤中的每一个。
如果你有一个allocator<string> alloc,你调用alloc.allocate,然后调用alloc.construct,这和new string()是一样的,当你调用alloc.destroy,然后调用alloc.deallocate,这和删除string指针是一样的。
因此,不会有任何额外的释放。对destroy的调用导致string释放它为它的缓冲区分配的任何内存,然后对deallocate的调用释放用于string对象本身的内存。
我没有完全理解你关于区域的问题。分配给存储string示例的内存和string分配给它的缓冲区的内存是不相关的。

idfiyjo8

idfiyjo83#

您可以继承std::string来添加一些print语句,以获得更好的理解(或者您需要调试并单步执行STL容器和函数)

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class MyString : public string {
    public:
    MyString() = default;
    ~MyString() { cout << "destructor of MyString:" << this << endl; }
};
int main(int argc, char **argv)
{
    const int cnt = 10;
    allocator<MyString> alloc;
    auto p = alloc.allocate(cnt);

    alloc.construct(p);
    for (int i = 0; i < cnt; ++i)
    {
        cout << p+i << endl; // print buffer address
    }

    alloc.destroy(p); // will it free buffer of string?

    cout << "alloctor destrory called" << endl;

    alloc.deallocate(p, cnt); // will it free buffer of string again?

    return 0;
}

在此处执行:
https://coliru.stacked-crooked.com/a/13302c7f0c4db07b

相关问题