使用malloc时调用类成员的构造函数

use*_*862 0 c++

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

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

#include <string>

class MyClass
{
    std::string value;
};

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

    system("pause");
}
Run Code Online (Sandbox Code Playgroud)

编辑:

阅读评论后,我想知道以下代码是否会按预期工作?不确定是否需要调用除整个类的析构函数之外的任何析构函数。

#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");
}
Run Code Online (Sandbox Code Playgroud)

Hum*_*ler 14

malloc 是一个 C 函数,不了解类、构造函数等。

如果您对使用此方法一无所知,您将必须始终new在每个对象上调用放置(注意:这需要包括 header <new>),并且还必须释放之前手动调用析构函数。您还需要存储从放置返回的指针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);
Run Code Online (Sandbox Code Playgroud)

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

#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;
};
Run Code Online (Sandbox Code Playgroud)

类似地,您可能希望在释放对象的同时销毁对象,这样资源就不会泄漏:

template <typename T>
void dispose(T* p)
{
    p->~T();
    std::free(p);
}
Run Code Online (Sandbox Code Playgroud)

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

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

dispose(cls);
Run Code Online (Sandbox Code Playgroud)

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

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

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


1如果您的“混合 C++/C”代码在某个时候公开了 C 接口,那么使用newanddelete而不是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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我误读了你的评论是“新”/“删除”而不是“新”...删除了我原来的回复。放置“new”[保证返回其输入](https://timsong-cpp.github.io/cppwp/n4659/new.delete.placement#2)。放置“new”实际上是一个无操作函数,它主要存在,以便在 C++ 中可以使用“new”表达式来启动对象的生命周期(因为所有动态对象的生命周期都以“new”开始) (2认同)