【问题标题】:How do I reserve space on the stack for a non-default constructible?如何在堆栈上为非默认构造保留空间?
【发布时间】:2015-03-28 16:17:43
【问题描述】:

我基本上会编写以下代码。我明白为什么它不能编译了。

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();

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

【问题讨论】:

  • 您是否还为A 提供了复制构造函数和赋值运算符?同样对于这种情况,您最好为您的类属性设置 getter/setter 函数,并使用默认构造函数。
  • 最简单的解决方案似乎是使用指针。您是否有理由需要它在堆栈上?
  • 将其推入向量中?
  • 你不能A instance("");

标签: c++ allocation default-constructor


【解决方案1】:

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

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

标准库提供了aligned_storage trait,因此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 };

【讨论】:

  • 好吧,这比我弱安置的新尝试好多了。
  • 放置构造函数之后谁来调用析构函数?
  • @LightnessRacesinOrbit,是的,如果char 缓冲区没有适当对齐,它可以避免潜在的对齐问题
  • 我做到了。这是一个手动条件表达式/ lambda,这很愚蠢。 juanchopanza 似乎是唯一注意到明显的、由语言提供的解决方案的人。
  • @Puppy,问题询问如何在堆栈上保留空间,所以这就是我的回答。如果类型是可移动构造的并且条件表达式可以工作,那么这就是 OP 应该使用的,但如果条件的两个分支需要多个语句,那么 aligned_storage+placement-new 解决方案仍然有效。
【解决方案2】:

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

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”。

【讨论】:

  • 那么它必须是可复制构造的。
  • @user3528438 或可移动构造。但是OP没有要求。任何一个。
  • 如果它真的是一次性函数,它可以是一个 lambda。
  • @Angew 或者只是A instance = something ? A("foo") : A(42);
【解决方案3】:

在一些简单的情况下,您可能能够摆脱这种标准的 C++ 语法:

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

您没有指定您使用的是哪个编译器,但在更复杂的情况下,这可以使用gcc 编译器特定扩展:

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

【讨论】:

  • 更多关于“复杂情况”的信息,好吗?第一种形式在什么情况下不起作用?
  • @user3528438,如果您在创建 A 对象之前需要完整的语句。条件运算符只允许表达式,GCC 扩展允许任意块。
  • @user3528438:您自己在 juanchopanza 的回答中指出了一个问题:如果 A 不是可复制构造的,那么此代码是非法的。
  • 这就是 lambdas 的用途,甚至是辅助函数。而且它可以移动构造,擦洗。
【解决方案4】:

这是一项安置新人的工作,但如果您重新审视自己的要求,几乎可以肯定可以采用更简单的解决方案。

#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();
}

(live demo)

这样你可以随时构造A,但存储仍然在堆栈上。

但是,您必须自己销毁对象(如上),这很讨厌。


免责声明:我的弱放置-新示例是幼稚的并且不是特别便携。 GCC 自己的 Jonathan Wakely 发布了一个更好的相同想法的例子。

【讨论】:

    【解决方案5】:
    std::experimental::optional<Foo> foo;
    if (condition){
      foo.emplace(arg1,arg2);
    }else{
      foo.emplace(zzz);
    }
    

    然后使用*foo 进行访问。 boost::optional 如果你没有 C++1z TS 实现,或者写你自己的optional

    在内部,它将使用诸如标准对齐存储和bool 之类的东西来保护“我已被创建”;或者可能是union。编译器可能会证明不需要bool,但我对此表示怀疑。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-02
      • 2015-07-15
      • 2010-09-13
      • 1970-01-01
      • 1970-01-01
      • 2012-04-22
      • 2014-06-29
      相关资源
      最近更新 更多