3

I have a class called 'Container' in which three (known at compile time) objects of class DontCopyMe must be stored. Class DontCopyMe has non-default constructor and deleted copy constructors. How can I initialize Containter?

Sample code:

#include <string>

class DontCopyMe
{
    public:
        DontCopyMe(const unsigned int SomeInt, const std::string & SomeString): SomeInt(SomeInt), SomeString(SomeString)
        {
        }
        DontCopyMe(const DontCopyMe &) = delete;
        DontCopyMe & operator = (const DontCopyMe &) = delete;
        DontCopyMe(DontCopyMe &&) = delete;
        DontCopyMe & operator = (DontCopyMe &&) = delete;

    private:
        const unsigned int SomeInt;
        const std::string SomeString;
};

class Container
{
    public:
        Container(): Array{{1, "A"}, {2, "B"}, {3, "C"}}
        {

        }

    private:
        DontCopyMe Array[3];
};

int main()
{
    Container C;
    return 0;
}

Of course I'm getting:

main.cpp: In constructor 'Container::Container()':                                                                                                                                                                                            
main.cpp:22:56: error: use of deleted function 'DontCopyMe::DontCopyMe(DontCopyMe&&)'                                                                                                                                                         
         Container(): Array{{1, "A"}, {2, "B"}, {3, "C"}}    
Cœur
  • 37,241
  • 25
  • 195
  • 267
peku33
  • 3,628
  • 3
  • 26
  • 44

1 Answers1

5

clang++ 3.6.2 compiles your code. g++ 5.2.0 does not.

According to this report, this is a bug in g++.


Here's a (nasty) workaround, for the moment: use std::aligned_storage, placement new and explicit destructor calls:

#include <type_traits>

class Container
{
    private:
        using Storage = typename std::aligned_storage
        <
            sizeof(DontCopyMe), 
            alignof(DontCopyMe)
        >::type;

    public:
        Container()
        {
            // Construct an instance of `DontCopyMe` in the memory
            // location starting at `&Array[0]`.
            new (&Array[0]) DontCopyMe{1, "A"};

            // ...
            new (&Array[1]) DontCopyMe{2, "B"};
            new (&Array[2]) DontCopyMe{3, "C"};

            // You can also (and should) use a for-loop.
        }  

        ~Container()
        {
            // Interpret the bytes at location `&Array[2]` as if
            // they were a `DontCopyMe` instance, then call the
            // `~DontCopyMe()` destructor on it.
            (reinterpret_cast<DontCopyMe*>(&Array[2]))->~DontCopyMe();

            // ...
            (reinterpret_cast<DontCopyMe*>(&Array[1]))->~DontCopyMe();
            (reinterpret_cast<DontCopyMe*>(&Array[0]))->~DontCopyMe();

            // You can also (and should) use a for-loop.
        }   

    private:
        Storage Array[3];
};

You'll have to implement move and copy operations for Containers that cleanup the aligned storage properly. You may want to wrap all aligned_storage-related code in an helper class.


You mentioned you require additional safety and constness. The best way to achieve this is wrapping std::aligned_storage in an helper class that will make sure you don't make mistakes.

Here's some code to get you started:

#include <type_traits>
#include <utility>
#include <cassert>

template<typename T, std::size_t TSize>
struct ASImmutableArray
{
    private:
        using ThisType = ASImmutableArray<T, TSize>;

        using Storage = typename std::aligned_storage
        <
            sizeof(T),
            alignof(T)
        >::type;

        Storage data[TSize];

        template<typename... Ts>
        void initAt(std::size_t mIndex, Ts&&... mXs)
        {
            assert(mIndex >= 0 && mIndex < TSize);
            // assert the data was not initialized
            new (&data[mIndex]) T(std::forward<Ts>(mXs)...);
        }

        void deinitAt(std::size_t mIndex)
        {
            assert(mIndex >= 0 && mIndex < TSize);
            // assert the data was actually initialized
            reinterpret_cast<T*>(&data[mIndex])->~T();
        }

    public:
        // ...


};

An idea is passing std::tuple instances in the constructor of ASImmutableArray, and creating T instances in place using placement new at the correct indices by expanding the tuples and forwarding their contents to T's constructor. The tuples would contain the same types as the types required to construct T.

You can also keep track of initialized/deinitialized items with an additional member boolean array (that can be disabled in release builds, and only used for verifying the correct usage of the class during development).

If you want an example of an (old) implementation of something similar, this is something I've written for one of my libraries.

You can also check out this tagged union implementation I've written to see an example on how I use debug-only member variables that have no overhead in release-builds for additional safety.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • This IS a solution, but absolutely not suitable for my needs. Right now I'm going to switch to clang and try again. – peku33 Aug 09 '15 at 17:36
  • @peku33: Out of curiosity, why isn't it suitable? – Vittorio Romeo Aug 09 '15 at 17:38
  • It places objects on heap. In target solution the array is also marked as const so assigning later than in initializer list is not possible. It also allows to skip initialization of object and it won't show any error. – peku33 Aug 09 '15 at 17:44
  • 3
    @peku33: Incorrect. It doesn't place the objects on the heap. `std::aligned_storage` is just a bunch of bytes. You can place them where you want, and in my example they're just a member of `Container`. It's equivalent to your desired code in terms of memory. You can solve your `const` issues and your `initialization issues` by creating a wrapper class around `std::aligned_storage` that makes sure you correctly initialize/deinitialize the memory. – Vittorio Romeo Aug 09 '15 at 17:56
  • 1
    Empirical proof that it does not make use of the heap: [ideone link](http://ideone.com/JCRvKX). – Vittorio Romeo Aug 09 '15 at 18:08