绕过C++中的构造函数

Bypassing constructor in C++

我正在尝试使用 C++ 通过 gcc-avr 进行 AVR 编程。主要问题是没有可用的 libc++,并且实现没有定义任何新的或删除的运算符。同样,没有 header 包括使用新的展示位置不是一个选项。

尝试分配新动态时 object 我很想这样做:

Class* p = reinterpret_cast<Class*>(malloc(sizeof(Class)));
p->Init();

其中 Init() 手动初始化所​​有内部变量。但这安全甚至可能吗?

我读到 object 在 C++ 中的构造有些复杂,但没有 new 或 delete 如何初始化动态分配的 object?


扩展上述问题。

使用标准 g++ 和 placement new 可以通过两种方式颠覆构造函数,假设 C++ 使用与 C 相同的直接内存分配方式(下面的代码示例)。

  1. 使用 placement new 初始化任何分配的内存。
  2. 使用class方法直接初始化分配的内存。

当然,这只有在假设为真时才成立:

如果上述成立,我可以不使用 malloc 分配内存并使用 reinterpret_cast 转换为正确的 class 并手动初始化它吗?当然,这既是 non-portable 又是 hack-ish,但我能看到的唯一其他方法是解决该问题,根本不使用动态分配的内存。

示例:

Class A {
    int i;
    long l;
    public:
        A() : i(1), l(2) {}
        int get_i() { return i; }
        void set_i(int x) { i = x; }
        long get_l() { return l; }
        void set_l(long y) { l = y; }
};

Class B {
     /* Identical to Class A, except constructor. */
     public B() : i(3), l(4) {}
};

int main() {
     A* a = (A*) ::operator new(sizeof(A));
     B* b = (B*) ::operator new(sizeof(B));

     /* Allocating A using B's constructor. */
     a = (A*) new (a) B();

     cout << a->get_i() << endl; // prints 3
     cout << a->get_l() << endl; // prints 4

     /* Setting b directly without constructing */
     b->set_i(5);
     b->set_l(6);

     cout << b->get_i() << endl; // prints 5
     cout << b->get_l() << endl; // prints 6

如果您声称的 C++ 编译器不支持 operator new,您应该能够简单地提供您自己的编译器,可以在 class 中或作为全局定义。这是来自 an article discussing operator new 的简单版本,稍作修改(在许多其他地方也可以找到相同的版本):

void* operator new(size_t sz) {
    void* mem = malloc(sz);
    if (mem)
        return mem;
    else
        throw std::bad_alloc();
}


void operator delete(void* ptr) {
    free(ptr);
}

关于 operator new 的更详细的讨论,特别是 class 特定的定义,也可以在 here.

中找到

从评论来看,似乎给出这样的定义,您的编译器就会很高兴地支持像这样的标准堆上对象创建:

auto a = std::make_shared<A>();
A *pa = new A{};

使用问题中代码片段中显示的 Init 方法的问题在于,要让它与继承一起正常工作可能会很痛苦,尤其是多重继承或虚拟继承,至少在某些情况下在对象构造期间可能会抛出。 C++ 语言有详尽的规则来确保在这种情况下构造函数会发生一些有用且可预测的事情;用普通函数复制它可能会很快变得棘手。

您的 malloc()-reinterprete_cast<>-init() 方法能否成功取决于您是否拥有虚拟 functions/inheritance。如果您的 class 中没有任何虚拟内容(它是一种普通的旧数据类型),您的方法将起作用。

但是,如果其中有任何虚拟内容,您的方法将惨遭失败:在这些情况下,C++ 会在您无法访问的 class 的数据布局中添加一个 v-table直接而无需深入研究未定义的行为。这个v-table指针一般是在构造函数为运行的时候设置的。由于在这方面您不能安全地模仿构造函数的行为,因此您必须实际调用构造函数。也就是说,你至少要使用placement-new。

正如 Christopher Creutzig 所建议的那样,提供 classless operator new() 是提供完整 C++ 功能的最简单方法。它是 new 表达式在内部使用的函数,用于提供可以调用构造函数以提供完全初始化对象的内存。


最后一点保证:只要你不在 struct 的末尾使用可变长度数组,就像这样

typedef struct foo {
    size_t arraySize;
    int array[];
} foo;

任何 class/struct 的大小完全是一个编译时间常数。