3

我有以下代码:

struct foo {};
void bar(foo *d) {
  new(d) foo(*d);
}

表达式是否使new(d) foo(*d)指向的对象d保持不变?更具体地说,如果类foo和其中递归包含的所有对象仅具有微不足道的复制构造函数,那么上述情况是否正确,那么是否new(d) foo(*d)保持*d不变?不正确的情况可能是,new在调用复制构造函数之前首先将内存清零。C++语言中有这样的子句吗?

编辑:有人想要这样做有重要的原因。考虑跨地址空间复制对象,例如,从 CPU 内存到 GPU 内存。一种解决方案是逐字节地处理对象。这在很多情况下都有效。如果该类具有虚方法,则逐字节复制复制 vtable 指针,该指针随后将指向一些 CPU 内存。可以在对象上使用上述表达式new(d) foo(*d)来强制编译器重置 vtable 指针。

4

6 回答 6

2

关于placement new是否将内存清零,它不会,它只是调用适当的构造函数,该构造函数将执行该构造函数所做的任何事情,这可能取决于它的定义方式是否将内存清零。在这种特殊情况下,您使用的是复制构造函数

在您提供的代码中,它是未定义的行为。要么d指向有效对象,要么不指向有效对象。如果它引用了一个有效对象,则您正在对已构造的对象调用构造函数,如果该对象具有非平凡的析构函数,则这是未定义的行为。如果它之前没有被初始化(即它没有引用一个foo对象),那么从它复制它是未定义的行为。

于 2012-05-07T23:14:52.583 回答
2

我刚刚在研究性能问题时遇到了这个问题。一些在包含大缓冲区的对象上使用放置新的代码出乎意料地慢。原因:placement new 在调用构造函数之前将内存清零。

我对该标准的阅读与其他答案一致:编译器不需要做任何特别的事情。

但是,gcc 4.9、gcc 5.3、clang 3.4、clang 3.8 和 Apple clang 在放置新案例中似乎都将内存归零。检查汇编器输出,memset在调用构造函数之前有一个显式调用。堆栈构造的对象不是零初始化的,所以它似乎不是构造函数在做这项工作。

检查来自 Dignus Systems/C++ for z/OS 的汇编器输出似乎也调用了一个库函数,大概是在做类似的事情(而且速度很慢)。

所以:放置 new 被允许将内存归零,并且似乎许多实现都将内存归零。

示例测试用例:

#include <new>
#include <cstdint>
#include <stdio.h>

struct Test {
    char b[4];

    void show(char const* prefix) {
        for (unsigned i = 0; i < sizeof(b); ++i)
            printf("%s index %d: %d\n", prefix, i, b[i]);
    }
};

int main()
{
    char* p = new char[sizeof(Test)];

    for (unsigned i = 0; i < sizeof(Test); ++i)
        p[i] = 'Q';

    Test* t1 = new(p) Test();
    Test t2;

    t1->show("t1");
    t2.show("t2");
}

示例输出(FreeBSD 上的 clang 3.4):

t1 index 0: 0
t1 index 1: 0
t1 index 2: 0
t1 index 3: 0
t2 index 0: 51
t2 index 1: -1
t2 index 2: 3
t2 index 3: 1
于 2016-08-02T10:51:46.360 回答
1

我相信这是未定义的行为:一旦存储对象的内存用于其他用途,对象的生命周期就会结束。当您进入this指针等于的复制构造函数时d,原始对象将不复存在(就语言而言),因此您在复制构造函数中有一个悬空引用。

当然更容易的是~foo()产生影响的情况,在这种情况下,您还有另一个未定义行为的原因。

于 2012-05-07T23:02:11.090 回答
1

是和否。

我认为我们在这里进入编译器定义的行为。但是,对于 Visual C++ 2019,如果没有默认构造函数,placement new 将清除数据。请参阅下面的片段。

int buffer[4];

class some_class
{
public:
    int a, b, c;
};

class some_class2
{
public:
    int a, b, c;

    some_class2()
    {
        ;
    }
};

buffer[0] = 1;
buffer[1] = 2;
buffer[2] = 3;
some_class* c = new (buffer) some_class();
std::cout << "placement new with a class clears it" << std::endl;
std::cout << c->a << " " << c->b << " " << c->c << std::endl;

buffer[0] = 1;
buffer[1] = 2;
buffer[2] = 3;
some_class2* d = new (buffer) some_class2();
std::cout << "placement new with a class that has a constructor does not clear it" << std::endl;
std::cout << d->a << " " << d->b << " " << d->c << std::endl;
于 2020-12-27T01:46:45.303 回答
0

放置 new 的唯一工作是在为尚未初始化的对象预留的内存上运行构造函数。如果您手动调用构造函数(尽管这是不可能的),那么它所做的就是您将得到的。

于 2012-05-07T22:50:17.910 回答
0

请注意,您正在调用对象的复制构造函数,并将其自身作为复制源。我希望这简直是疯了。谁知道。我在标准中看不到任何让我期待任何东西的东西。

于 2012-05-07T22:55:09.173 回答