如何在堆栈上为非默认构造对象保留空间

How do I reserve space on the stack for a non-default constructible?

本文关键字:对象 保留 空间 默认 堆栈      更新时间:2023-10-16

我基本上会编写以下代码。我理解为什么它不能编译。

A instance; // A is a non-default-constructable type and therefore can't be allocated like this
if (something)
{
    instance = A("foo"); // use a constructor X
}
else
{
    instance = A(42); // use *another* constructor Y
}
instance.do_something();

有没有一种方法可以在不涉及堆分配的情况下实现这种行为?

有比在堆栈上显式保留空间更好、更干净的方法来解决问题,例如使用条件表达式。

但是,如果类型不可移动构造,或者您有更复杂的条件,这意味着确实需要在堆栈上保留空间,以便稍后在两个不同的地方构造一些东西,您可以使用下面的解决方案。

标准库提供了aligned_storage特性,因此aligned_storage<T>::type是一种POD类型,具有合适的大小和对齐方式,用于存储T,因此您可以使用它来保留空间,然后使用placement new将对象构造到该缓冲区中:

std::aligned_storage<A>::type buf;
A* ptr;
if (cond)
{
  // ...
  ptr = ::new (&buf) A("foo");
}
else
{
  // ...
  ptr = ::new (&buf) A(42);
}
A& instance = *ptr;

只需记住手动销毁它,您可以使用unique_ptr和自定义删除程序:

struct destroy_A {
  void operator()(A* a) const { a->~A(); }
};
std::unique_ptr<A, destroy_A> cleanup(ptr);

或者使用lambda,尽管这会浪费堆栈上的额外指针;-)

std::unique_ptr<A, void(*)(A*)> cleanup(ptr, [](A* a){ a->~A();});

或者甚至只是一个专用的本地类型,而不是使用unique_ptr

struct Cleanup {
  A* a;
  ~Cleanup() { a->~A(); }
} cleanup = { ptr };

假设您想多次执行此操作,则可以使用辅助函数:

A do_stuff(bool flg)
{
  return flg ? A("foo") : A(42);
}

然后

A instance = do_stuff(something);

否则,您可以使用条件运算符表达式*:进行初始化

A instance = something ? A("foo") : A(42);

*这是一个条件运算符如何不"像if-else"的例子

在一些简单的情况下,您可以使用以下标准C++语法:

A instance=something ? A("foo"):A(42);

您没有指定要使用的编译器,但在更复杂的情况下,使用gcc编译器特定的扩展是可行的

A instance=({
       something ? A("foo"):A(42);
});

这是一项新职位的工作,尽管如果您重新审视您的需求,几乎可以肯定会有更简单的解决方案。

#include <iostream>
struct A
{
    A(const std::string& str) : str(str), num(-1)  {};
    A(const int num)          : str(""),  num(num) {};
    void do_something()
    {
        std::cout << str << ' ' << num << 'n';
    }
    const std::string str;
    const int num;
};
const bool something = true;   // change to false to see alternative behaviour
int main()
{
    char storage[sizeof(A)];
    A* instance = 0;
    if (something)
        instance = new (storage) A("foo");
    else
        instance = new (storage) A(42);
    instance->do_something();
    instance->~A();
}

(现场演示)

通过这种方式,您可以随时构建A,但存储仍在堆栈上。

然而,你必须自己销毁对象(如上所述),这是令人讨厌的。


免责声明:我的弱放置新示例很天真,不是特别便携。GCC自己的Jonathan Wakely发布了一个更好的例子来说明同样的想法。

std::experimental::optional<Foo> foo;
if (condition){
  foo.emplace(arg1,arg2);
}else{
  foo.emplace(zzz);
}

则使用CCD_ 9进行访问。boost::optional(如果您没有C++1z TS实现),或者编写自己的optional

在内部,它将使用类似std对齐的存储和bool来保护"我已经创建了吗";或者可能是CCD_ 13。编译器可能会证明不需要bool,但我对此表示怀疑

实现可以从github下载,也可以使用boost。