【问题标题】:Calling constructor of class member when using malloc使用 malloc 时调用类成员的构造函数
【发布时间】:2021-01-03 16:01:46
【问题描述】:

由于 std::string 的构造函数从未被调用,以下代码中断,因为对 malloc 的调用似乎没有调用类成员的构造函数。

如何在不使用 new 的情况下调用 std::string 的构造函数?还是在分配内存后我必须在整个结构上使用placement new?

#include <string>

class MyClass
{
    std::string value;
};

int main()
{
    MyClass* cls = (MyClass*)malloc(sizeof(MyClass));
    cls->value = "hello";

    system("pause");
}

编辑:

看完了cmets,我想知道下面的代码是否能按预期工作?不确定是否需要调用除整个类之外的任何析构函数。

#include <string>

class MyClass
{
    std::string value;
};

int main()
{
    MyClass* cls = (MyClass*)malloc(sizeof(MyClass));
    cls = new(cls) MyClass;
    cls->value = "hello";

    cls->~MyClass();
    free(cls);

    system("pause");
}

【问题讨论】:

标签: c++


【解决方案1】:

malloc是一个C函数,对类、构造函数等一无所知。

如果您对使用它一无所知,您将必须始终在每个对象上调用放置new(注意:这需要包括标题&lt;new&gt;),并且在释放之前手动调用析构函数。您还需要存储从位置 new 返回的指针,以避免未定义的行为(因为从技术上讲,这是唯一指向对象生命周期开始的指针)。

正如您在编辑中所做的那样,它看起来像:

auto cls = static_cast<MyClass*>(std::malloc(sizeof(MyClass)));
cls = new(cls) MyClass(/* any arguments here ... */);
cls->value = "hello";

cls->~MyClass();
std::free(cls);

但是,请注意,使用malloc 实际上会引入与异常安全 相关的其他问题。如果您的placement-new 调用引发异常,std::malloc 分配的内存将泄漏——这很糟糕。如果malloc 返回null,您也不希望放置newnullptr 上运行,并且需要传达此错误- 可能通过异常。您可能希望将其包装到一个帮助程序中,以确保这些情况不会发生:

#include <new>     // placement-new, std::bad_alloc
#include <utility> // std::forward
#include <cstdlib> // std::malloc, std::free

template <typename T, typename...Args>
T* make(Args&&...args)
{
    auto* p = static_cast<T*>(std::malloc(sizeof(T)));

    // Placement new can't operate on nullptr
    if (p == nullptr) {
        // alternatively, this could be 'return nullptr' if not using exceptions
        throw std::bad_alloc{}; 
    }

    try {
        p = new (p) T(std::forward<Args>(args)...);
    } catch (...) {
        std::free(p);
        throw; // rethrow the exception here
    }
    return p;
};

同样,您可能希望有一种方法在释放对象时销毁它们,以免资源泄漏:

template <typename T>
void dispose(T* p)
{
    p->~T();
    std::free(p);
}

使用这些实用程序,上面的示例现在变为:

auto cls = make<MyClass>(/* any arguments here ... */);
cls->value = "hello";

dispose(cls);

如果这看起来像一个非常多的样板,这是因为这正是 newdelete 已经为您所做的。如果可以,在处理 C++ 类型时使用这些工具会更好。这也使您可以轻松地继续使用其他 RAII 包装器,例如智能指针,而无需手动指定分配器 (shared_ptr) 或删除器 (unique_ptr)。

在使用 C++ 代码时,IMO 确实没有充分的理由阻止使用 newdelete 来支持 malloc/free。如果您有一个库,该库只公开了一个仅 C 接口,但在 C++ 代码中实现——实际上,最好将分配内部化并切换到 newdelete,而不是使用mallocfree 这样您就可以省去麻烦。1

要注意的另一件事是,如果您系统中分配的类型通过alignas 自定义对齐超过alignof(std::max_align_t),则使用std::malloc 将是未定义的行为,因为它将无法满足对齐要求。这是new/delete 会为您处理的另一件事。


1 如果您的“混合 C++/C”代码在某些时候暴露了 C 接口,那么使用 newdelete 而不是 malloc 会更干净,并简单地在您的 API 上显式公开创建/删除函数。例如:

// C header:

extern "C" 
my_class* make_my_class(void);

extern "C"
void dispose_my_class(my_class* p);

// C++ implementation:

extern "C"
my_class* make_my_class(void)
{
    return new my_class{ ... };
}

extern "C"
void dispose_my_class(my_class* p)
{
    delete p;
}

这通常是 C/C++ 互操作所采用的方法。

【讨论】:

  • 我想知道,标准是否保证放置new返回的指针与malloc返回的内存地址相同?或者是否存在某种(理论上的)可能性,对齐会导致这两个指针指向不同的内存地址(这可能会中断对free的调用)?
  • 我将您的评论误读为new/delete 而不是放置new... 删除了我原来的回复。展示位置 newguaranteed to return its input。放置new 实际上是一个无操作函数,主要存在以便在 C++ 中可以使用“new 表达式”来启动对象的生命周期(因为所有动态对象的生命周期都以 new 开头)
  • 也许std::aligned_alloc 可以帮助回答最后一部分(如果使用c++17)?
  • @TedLyngmo 我也这么想,但没有添加这个细节,因为 OP 明确提到了强制 malloc/free... 有很多比使用 @ 更好的选择987654370@ 解决这个特殊问题(例如,只需使用 new/delete
猜你喜欢
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 1970-01-01
  • 2017-09-07
  • 1970-01-01
  • 2023-02-09
相关资源
最近更新 更多