Jack Sabbath
Jack Sabbath

Reputation: 1478

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

I would basically write the following piece of code. I understand why it can't compile.

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

Is there a way to achieve this behaviour without involving heap-allocation?

Upvotes: 7

Views: 633

Answers (5)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

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

then use *foo for access. boost::optional if you do not have the C++1z TS implementation, or write your own optional.

Internally, it will use something like std aligned storage and a bool to guard "have I been created"; or maybe a union. It may be possible for the compiler to prove the bool is not needed, but I doubt it.

An implementation can be downloaded from github or you can use boost.

Upvotes: 3

Jonathan Wakely
Jonathan Wakely

Reputation: 171263

There are better, cleaner ways to solve the problem than explicitly reserving space on the stack, such as using a conditional expression.

However if the type is not move constructible, or you have more complicated conditions that mean you really do need to reserve space on the stack to construct something later in two different places, you can use the solution below.

The standard library provides the aligned_storage trait, such that aligned_storage<T>::type is a POD type of the right size and alignment for storing a T, so you can use that to reserve the space, then use placement-new to construct an object into that buffer:

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

Just remember to destroy it manually too, which you could do with a unique_ptr and custom deleter:

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

Or using a lambda, although this wastes an extra pointer on the stack ;-)

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

Or even just a dedicated local type instead of using unique_ptr

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

Upvotes: 12

juanchopanza
juanchopanza

Reputation: 227400

Assuming you want to do this more than once, you can use a helper function:

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

Then

A instance = do_stuff(something);

Otherwise you can initialize using a conditional operator expression*:

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

* This is an example of how the conditional operator is not "just like an if-else".

Upvotes: 9

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385144

This is a job for placement new, though there are almost certainly simpler solutions you could employ if you revisit your requirements.

#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)

This way you can construct the A whenever you like, but the storage is still on the stack.

However, you have to destroy the object yourself (as above), which is nasty.


Disclaimer: My weak placement-new example is naive and not particularly portable. GCC's own Jonathan Wakely posted a much better example of the same idea.

Upvotes: 3

Sam Varshavchik
Sam Varshavchik

Reputation: 118330

In some simple cases you may be able to get away with this standard C++ syntax:

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

You did not specify which compiler you're using, but in more complicated situations, this is doable using the gcc compiler-specific extension:

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

Upvotes: 3

Related Questions