问题
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]$
回答1:
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.
回答2:
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()
{}
回答3:
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:
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.
来源:https://stackoverflow.com/questions/14543554/why-cant-i-initialize-an-array-of-objects-if-they-have-private-copy-constructor