是否可以将堆栈对象地址添加到新的位置?

时间:2015-07-15 14:02:07

标签: c++ stack new-operator heap-memory placement-new

忽略这种做法的有用性。 (当然,欢迎现实生活中的例子。)

例如,以下程序输出a的正确值:

#include <iostream>

using namespace std;

int main()
{
  int a = 11111;
  int i = 30;

  int* pi = new (&i) int();

  cout << a << " " << endl;
}

但是新的分配不应该创建一些与i相邻的簿记信息(用于正确的后续解除分配),在这种情况下应该破坏i周围的堆栈?

3 个答案:

答案 0 :(得分:6)

是的,使用指向堆栈上对象的指针执行placement-new是完全可以的。它只会使用该特定指针来构造对象.Place-new isn实际上分配任何内存 - 您已经提供了该部分。它只做构建。随后的删除实际上不会是delete - there is no placement delete - 因为您需要做的就是调用对象的析构函数。实际的内存由其他东西管理 - 在这种情况下是你的堆栈对象。

例如,给定这种简单类型:

struct A {
    A(int i) 
    : i(i)
    {
        std::cout << "make an A\n";
    }

    ~A() {
        std::cout << "delete an A\n";
    }

    int i;
};

以下是完全合理,表现良好的代码:

char buf[] = {'x', 'x', 'x', 'x', 0};
std::cout << buf << std::endl;  // xxxx
auto a = new (buf) A{'a'};      // make an A
std::cout << a->i << std::endl; // 97
a->~A();                        // delete an A

唯一无效的情况是,如果你的贴牌新对象比你新推出的内存要长 - 原因与返回悬空指针总是坏的相同:

A* getAnA(int i) {
    char buf[4];
    return new (buf) A(5); // oops
}

答案 1 :(得分:1)

不,因为你没有delete一个已经放置新位置的对象,你可以手动调用它的析构函数。

struct A {
    A() { std::cout << "A()\n"; }
    ~A() { std::cout << "~A()\n"; }
};

int main()
{
    alignas(A) char storage[sizeof(A)];

    A *a = new (storage) A;
    std::cout << "hi!\n";

    a->~A();
    std::cout << "bye!\n";
}

输出:

A()
hi!
~A()
bye!

在你的情况下,也没有必要调用析构函数,因为int是可以轻易破坏的,这意味着它的析构函数无论如何都是无操作的。

请注意不要在仍处于活动状态的对象上调用placement-new,因为它不仅会破坏其状态,而且还会调用它的析构函数两次(一次是手动调用它时,也是原始对象时)应该被删除,例如在其范围的末尾)。

答案 2 :(得分:0)

新的安置是建筑,而不是分配,因此没有簿记信息可以担心。

我现在可以想到一个可能的用例,尽管它(以这种形式)将是一个错误的封装示例:

#include <iostream>
using namespace std;
struct Thing {
 Thing (int value) {
  cout << "such an awesome " << value << endl;
 }
};
union Union {
 Union (){}
 Thing thing;
};
int main (int, char **) {
 Union u;
 bool yes;
 cin >> yes;
 if (yes) {
  new (&(u.thing)) Thing(42);
 }
 return 0;
}

Live here

尽管在某些成员函数中隐藏了placement new,但仍然会在堆栈中进行构造。

所以:我没有查看标准,但是不能想到为什么不允许在堆栈上放置新内容。

一个真实世界的例子应该在https://github.com/beark/ftl的源代码中的某个位置,在它们的递归联合中,用于求和类型。