3

I want to make a template that contains a private member that should be left unconstructed, until it is explicitly constructed using placement new.

How can this be achieved with C++14?

Somewhat like this:

template <typename T>
class Container {
    private:
        T member; //should be left unconstructed until construct() is called
    public:
        Container() = default;
        void construct() {
            new (&this->member) T();
        }
};
FSMaxB
  • 2,280
  • 3
  • 22
  • 41
  • 2
    Class construction is an all or nothing proposition. Either the entire class gets constructed, or nothing gets constructed. There is no middle ground. What is the real problem are you trying to solve. No, not the one about not constructing a class member, but the problem whose solution you believe involves not constructing a single class member. – Sam Varshavchik Aug 25 '17 at 19:02
  • @SamVarshavchik Implementing std::optional – FSMaxB Aug 25 '17 at 19:03
  • 1
    I hate saying this but.. `malloc`. See: https://stackoverflow.com/questions/8959635/malloc-placement-new-vs-new – NathanOliver Aug 25 '17 at 19:04
  • 2
    Use non-public `std::aligned_storage` with a `reinterpret_cast`ing getter/setter. Even better, wrap that into an another class, adding double construction/destruction protection. – HolyBlackCat Aug 25 '17 at 19:04
  • It is UB to construct a object over an already existing object. So placement new is not what you can do here! – Klaus Aug 25 '17 at 19:04
  • 2
    `std::optional` is not implemented as a class member. It is implemented by declaring a `char` array of `sizeof(T)`, and that's the class member, then using placement new to construct it. Your class member is not T, but a `char` array. – Sam Varshavchik Aug 25 '17 at 19:05
  • @HolyBlackCat Yes std::aligned_storage is what I was looking for. Mind posting it as an answer? – FSMaxB Aug 25 '17 at 19:05
  • @SamVarshavchik That wouldn't align properly. – FSMaxB Aug 25 '17 at 19:06
  • @FSMaxB I don't want to bother writing a full answer about that. You can do that if you want. – HolyBlackCat Aug 25 '17 at 19:06
  • Then use `std::aligned_storage`. – Sam Varshavchik Aug 25 '17 at 19:07
  • @FSMaxB You could apply `alignas()` to the `char` array. That's how `aligned_storage` works. – HolyBlackCat Aug 25 '17 at 19:07
  • std::optional must be implemented using a union, because it must be usable in constant expressions. – T.C. Aug 26 '17 at 17:48

2 Answers2

5

There is a cleaner way than Nir's, using union-like class:

template <typename T>
class Container {
    private:
        bool is_constructed = false;
        union { T member; };
    public:
        Container() {}
        ~Container() {
            if (is_constructed) {
                member.~T();
            }
        }
        void construct() {
            assert(!is_constructed);
            new (&this->member) T();
            is_constructed = true;
        }
};

You might also want to add other constructors/assignment operators. Of course, in this simple case std::optional does exactly the same thing but much cleaner. This is still useful if you want to dodge the overhead of the bool like when if is_constructed can be encoded in other state, or if there are multiple members governed by the same flag.

yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
  • Wow, I would have never thought of using a union, but now that I see it it's quite simple and straightforward. – FSMaxB Aug 25 '17 at 19:47
  • @FSMaxB It's actually not. You simply substitute having to know all of the rules for unions involving non-trivial types. Since this basically never comes up I'd rather stick with placement new and aligned storage. Also, the comment about dodging the overhead of 'bool' is rather off base, whether you do that is 100% orthogonal to union vs aligned_storage. – Nir Friedman Aug 25 '17 at 19:49
  • @NirFriedman It's true that I don't really understand the comment about dodging the overhead of the bool. That doesn't really make sense. Coming from a C background using a union did make sense to me. But now that I think about it it might even be undefined behavior to just construct the union member. – FSMaxB Aug 25 '17 at 19:54
  • @NirFriedman I meant in comparison to `std::optional`: `struct X { std::optional a, b, c; };` vs `struct Y { union { T a; }; union { T b; }; union { T c; }; bool has_a : 1; bool has_b : 1; bool has_c : 1; };`. For any `T`, `sizeof(Y) < sizeof(X)`. – yuri kilochek Aug 25 '17 at 20:15
  • @FSMaxB it is well defined. Unions of non-POD types would have been useless otherwise. – yuri kilochek Aug 25 '17 at 20:16
  • @NirFriedman It's very simple: if the data member has a nontrivial special member, then the union's (or the containing class, for anonymous unions) corresponding special member function must be provided; if defaulted, it is defined as deleted. Getting an error when you forgot to write a special member is far better than the silent memcpy-esque semantics of `aligned_storage`. – T.C. Aug 26 '17 at 02:48
3
template <typename T>
class Container {
    private:
        std::aligned_storage_t<sizeof(T), alignof(T)> data;
        bool is_active = false;
        T& as_type() { return reinterpret_cast<T&>(data); }
    public:
        Container() = default;
        void construct() {
            if (is_active) throw ...;
            new (&data) T();
            is_active = true;
    }
};

Using as_type before the type is constructed (or after it is destructed) will be UB. I probably wouldn't actually have a member construct though. I would just actually focus on writing the different members that you need. In this case you should probably just have a constructor that takes a T:

Container::Container(const T& t)
    : is_active(true)
{
    new (&datamember) T(t);
}

One thing to note is that as you keep implementing, you'll start to see some of the downsides of this approach (in its simplest form). You have to have a boolean of course, to check whether things are already constructed, and so on. So you'll have destructor, copy, move operators, that check a boolean, and then do something.

However, none of this is needed if the contained type is itself trivial, like an integer or something. This can be a really large performance hit in some situations. So the standard mandates that these sort of traits carry through from the contained type to the optional. That is, if the contained type is trivially destructible, then so will be an optional on that type. Therefore:

std::cerr << std::is_trivially_destructible_v<std::optional<int>>;

Will print out 1. Implementing this however requires more complexity, tricks like conditional inheritance for example (just one possible approach).

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • I don't know who downvoted this, but I guess because it would be UB if `construct` were called twice. Needs a boolean flag if it has been constructed I guess. – FSMaxB Aug 25 '17 at 19:12
  • Yes.... I assumed you were going to work out the other parts of the optional implementation and just needed help with this one bit. Did the downvoter expect me to implement all of optional in my answer? – Nir Friedman Aug 25 '17 at 19:13
  • I will accept this answer as soon as I can (after 10 mins). Adding the boolean flag would be good because it avoids other people looking for this from missing the fact that calling `construct` twice would be UB. – FSMaxB Aug 25 '17 at 19:15
  • Not my downvote, but you are using `aligned_storage` completely wrong. – T.C. Aug 25 '17 at 19:39
  • @T.C. Can you elaborate? – Nir Friedman Aug 25 '17 at 19:39
  • Just...check its documentation. Look at the template parameters it takes. – T.C. Aug 25 '17 at 19:40
  • std::aligned_storage – FSMaxB Aug 25 '17 at 19:42
  • @T.C. Yeah, sorry, been a while since I actually used it. Thanks for the catch. – Nir Friedman Aug 25 '17 at 19:43
  • 2
    "Will print out 1 in most standard library implementations." I should hope it prints 1 in all standard library implementations given that it's [mandated](http://eel.is/c++draft/optional.dtor#2)... – Barry Aug 25 '17 at 19:48