Why can't I initialize an array of objects if

2019-03-01 18:17发布

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 as well:

class Irritating
{
    public:  Irritating() {}
    private: Irritating(const Irritating& other) {}
};

const Irritating singleton;                // Works just fine.
const Irritating array[] = {Irritating()}; // Compilation error.

int main()
{
    return 0;
}

Trying to compile this produces the following error (GCC version thrown in just in case):

[holt@Michaela irritating]$ g++ --version
g++ (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2)
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

[holt@Michaela irritating]$ g++ test.cpp
test.cpp:4:11: error: ‘Irritating::Irritating(const Irritating&)’ is private
test.cpp:8:41: error: within this context
[holt@Michaela irritating]$ 

The offending object, unfortunately, is from an external library and outside my control. My current workaround is to use an array of pointers; it works, but it feels a bit hackish and adds a needless layer of indirection. Is there a better way to do this?

Also: The array is constant and global (well, class-static in the actual code); why isn't it being initialized in place? Is this expected C++ behaviour, or a bug/quirk of GCC?

Update: Installed Clang just to see if it would agree with GCC. Sadly, it did:

[holt@Michaela irritating]$ clang test.cpp
test.cpp:8:29: warning: C++98 requires an accessible copy constructor for class 'Irritating' when binding a reference to a temporary; was private
      [-Wbind-to-temporary-copy]
const Irritating array[] = {Irritating()};
                            ^
test.cpp:4:11: note: declared private here
        private: Irritating(const Irritating& other) {}
                 ^
test.cpp:8:29: error: calling a private constructor of class 'Irritating'
const Irritating array[] = {Irritating()};
                            ^
test.cpp:4:11: note: declared private here
        private: Irritating(const Irritating& other) {}
                 ^
1 warning and 1 error generated.
[holt@Michaela irritating]$

4条回答
神经病院院长
2楼-- · 2019-03-01 18:21

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.

查看更多
相关推荐>>
3楼-- · 2019-03-01 18:23

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

查看更多
我欲成王,谁敢阻挡
4楼-- · 2019-03-01 18:24
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()
{}
查看更多
唯我独甜
5楼-- · 2019-03-01 18:46

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.

查看更多
登录 后发表回答