Why can't I initialize an array of objects if they have private copy constructors?

前端 未结 4 379
夕颜
夕颜 2021-01-24 08:34

I just ran across some unexpected and frustrating behaviour while working on a C++ project. My actual code is a tad more complicated, but the following example captures it just

相关标签:
4条回答
  • 2021-01-24 08:54

    Tried just creating the array with a size? Default ctor should be called.

    As in this source

    struct foo {
       int x;
       foo():x(1) {}
    private:
       foo( foo const& ) {}
    };
    
    foo array[10];
    
    #include <iostream>
    int main() {
       for (auto&& i:array) {
          std::cout << i.x << "\n";
       }
    }
    

    which demonstrates initialized foo in an array with no default copy constructor.

    If your problem is that you actually want to construct the foo with a non-default constructor, this can be done as well, but it is much harder, and that isn't what your question asked. In any case, here is a very, very rough sketch of the kind of stuff needed to create an array-like structure that supports emplaced construction of its elements. It is far from finished or compiling, but the basic technique should be sound:

    #include <cstddef>
    #include <utility>
    #include <type_traits>
    
    
    template<typename... Args>
    struct types {};
    
    template<typename types, typename=void>
    struct emplacer;
    
    template<typename T>
    struct remove_refref {
      typedef T type;
    };
    template<typename T>
    struct remove_refref<T&&> {
      typedef T type; 
    };
    
    template<typename A1, typename... Args>
    struct emplacer< types<A1, Args...>, void>:
      emplacer< types<Args...> >
    {
      typename remove_refref<A1>::type val;
      emplacer( A1 arg, Args... args ):
        emplacer< types<Args...>, index+1 >( std::forward(args)... ),
        val( std::forward(arg) )
      {}
    };
    
    template< std::size_t n >
    struct extract {
      template< typename A1, typename... Args >
      A1&& from( emplacer<types<A1, Args...>&& e ) {
        return extract<n-1>::from( emplacer<types<Args...>>&&(e) );
      }
    };
    template<>
    struct extract<0> {
      template< typename A1, typename... Args >
      A1&& from( emplacer<types<A1, Args...>&& e ) {
        return std::move( e.val );
      }
    };
    
    template<std::size_t... v>
    struct seq {};
    template<std::size_t n, std::size_t... tail>
    struct make_seq: make_seq<n-1, n-1, tail...> {};
    template<std::size_t n, std::size_t... tail>
    struct make_seq<0, tail...> {
      typedef seq<tail...> type;
      type val() { return type(); }
    };
    struct nothing {};
    template<typename T, typename... Args, std::size_t... indexes>
    nothing construct( T* src, emplacer<types<Args...>>&& e, seq<indexes...> s = make_seq< sizeof...(Args) >::val() ) {
      new(src)T( std::move( extract<indexes>( std::move(e) ))... );
    }
    
    template<typename... Args>
    emplacer< types<Args...> > emplace( Args&&... a ) {
      return emplacer< types<Args...> >( std::forward(a)... );
    }
    
    template<typename T, std::size_t n>
    struct my_array {
    private:
      union T_mem {
        T t;
        char x;
        T_mem():x(0) {}
      };
      T_mem buff[n];
      template<typename... nothings>
      void do_nothing( nothings...&& ) {}
      template<typename... emplacers, std::size_t... indexes>
      my_array( emplacers&&... em, seq<indexes...> s=make_seq< sizeof...(emplacers) >::val() ) {
        do_nothing( construct( &buff[indexes].t, em)... );
      }
      ~my_array() {
        for( auto&& v:buff) {
          v.t.~T();
        }
      }
      T& operator[](std::size_t n) { return buff[n].t; }
      // etc
    };
    

    The idea is that we create an array like construct that is actually an array of union to both T and a char. We thus avoid actually constructing our T, while still having proper alignment and such for one. Assuming char has no non-trivial alignment, the resulting buffer is binary-compatible with a T[].

    We then take as a construction argument emplacer objects, which act as packages for arbitrary construction arguments. For annoying reasons, these objects need to create a temporary copy of some of their parameters (I cannot figure out how to avoid the lifetime issues... maybe I'm missing something).

    The constructor of the my_array takes any number of emplacers and proceeds to construct the contents of buff based on their arguments.

    You'd create your array something like this:

    my_array< Foo, 10 > arr = {
      emplacer( a, b, c ),
      emplacer( x, y, z ),
      ...
    };
    

    a bit more work would allow default construction of uninitialized objects in the array.

    But this is really, really tricky to write correctly.

    0 讨论(0)
  • 2021-01-24 08:56

    Because individual array elements are initialized by copy-initialization from the initializers specified through = {...} syntax. See 8.5/12 (C++03)

    The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization

    Copy-initialization requires copy constructor (even if it won't actually use it).

    In practice, if you make your code compile by making the copy constructor public, the compiler will probably end up initializing your array elements in place, without using the copy constructor. Nevertheless, the formal rules of abstract language call for copy-initialization in this context.

    0 讨论(0)
  • 2021-01-24 09:01
    class Irritating
    {
        public:  Irritating() {}
        private: Irritating(const Irritating& other) {}
    };
    
    enum DefaultConstruct { defaultConstruct };
    
    class MaybeTooClever
        : public Irritating
    {
    public:
        MaybeTooClever( DefaultConstruct = defaultConstruct ) {}
    #ifdef __GNUC__
    public:
        MaybeTooClever( MaybeTooClever const& other );      // No such.
    #else
    private:
        MaybeTooClever( MaybeTooClever const& other );      // No such.
    #endif
    };    
    
    static MaybeTooClever const array[] = { defaultConstruct };
    
    int main()
    {}
    
    0 讨论(0)
  • 2021-01-24 09:02

    Assuming the copy constructor of Irritating is disabled because it is expensive than perhaps it is best to manage them by reference:

    vector<unique_ptr<Irritating>> V = { new Irritating(), ... };
    

    You could use shared_ptr instead of unique_ptr depending on the usage pattern.

    (If you could modify Irritating you could give it a move constructor, take a look at move semantics)

    If you really want them constructed in place than you could use aligned_storage to make an array of storage for them and then placement new them in place. This would produce almost identical compiled code to what you want to do with your original request, but it is a little messier:

    aligned_storage <sizeof(Irritating), alignment_of<Irritating>::value>::type data[N];
    new ((Irritating*) data+0) Irritating(...);
    new ((Irritating*) data+1) Irritating(...);
    new ((Irritating*) data+2) Irritating(...);
    ...
    new ((Irritating*) data+N-1) Irritating(...);
    

    (Dont forget to placement delete them at program exit.)

    0 讨论(0)
提交回复
热议问题