我在处理C ++项目时遇到了一些意外和令人沮丧的行为。我的实际代码有点复杂,但以下示例也捕获了它:
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;
}
尝试编译它会产生以下错误(以防万一的方式抛出GCC版本):
[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]$
不幸的是,违规对象来自外部图书馆,不受我控制。我目前的解决方法是使用指针数组;它有效,但感觉有点hackish并添加了一个不必要的间接层。有更好的方法吗?
另外:数组是常量和全局的(实际代码中是类静态的);为什么不进行初始化?这是预期的C ++行为,还是GCC的错误/怪癖?
更新:安装Clang只是为了看看它是否会同意GCC。可悲的是,它确实:
[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]$
答案 0 :(得分:4)
因为通过 copy-initialization 从通过= {...}
语法指定的初始化程序初始化各个数组元素。见8.5 / 12(C ++ 03)
参数传递中发生的初始化,函数返回, 抛出异常(15.1),处理异常(15.3),和 大括号括起的初始化列表(8.5.1)称为复制初始化
复制初始化需要复制构造函数(即使它实际上不会使用它)。
实际上,如果通过将复制构造函数设为public来编译代码,编译器可能会在不使用复制构造函数的情况下最终初始化数组元素。然而,抽象语言的形式规则要求在此上下文中进行复制初始化。
答案 1 :(得分:1)
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()
{}
答案 2 :(得分:1)
假设Irritating
的复制构造函数被禁用,因为它的价格昂贵,最好通过引用来管理它们:
vector<unique_ptr<Irritating>> V = { new Irritating(), ... };
您可以使用shared_ptr
代替unique_ptr
,具体取决于使用模式。
(如果你可以修改Irritating你可以给它一个移动构造函数,看看移动语义)
如果确实希望它们构建到位,那么您可以使用aligned_storage
为它们创建一个存储阵列,然后将它们放置到位。这会产生几乎完全相同的编译代码,而不是原始请求,但它有点麻烦:
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(...);
(不要忘记在程序退出时将其删除。)
答案 3 :(得分:-4)
尝试用大小创建数组?应该调用默认的ctor。
与此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";
}
}
演示了没有默认复制构造函数的数组中的初始化foo
。
如果您的问题是您实际上想要使用非默认构造函数构造foo
,那么也可以这样做,但这要困难得多,这不是您的问题所要求的。在任何情况下,这里都是一个非常非常粗略的草图,描述了创建一个类似阵列的结构所需的东西,它支持其元素的布设结构。它远未完成或编译,但基本技术应该是合理的:
#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
};
我们的想法是创建一个类似于数组的数组,实际上是union
到T
和char
的数组。因此,我们避免实际构建我们的T
,同时仍然具有适当的对齐方式。假设char
没有非平凡的对齐,则生成的缓冲区与T[]
二进制兼容。
然后我们将构造参数emplacer
作为对象,它们作为任意构造参数的包。出于恼人的原因,这些对象需要创建一些参数的临时副本(我无法弄清楚如何避免生命周期问题......也许我错过了一些东西)。
my_array
的构造函数接受任意数量的emplacers
并继续根据其参数构造buff
的内容。
你可以创建这样的数组:
my_array< Foo, 10 > arr = {
emplacer( a, b, c ),
emplacer( x, y, z ),
...
};
更多的工作将允许在数组中默认构造未初始化的对象。
但要正确编写这真的非常棘手。