如果具有私有拷贝构造函数,为什么我不能初始化对象数组?

时间:2013-01-27 01:34:19

标签: c++ g++ initialization copy-constructor

我在处理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]$

4 个答案:

答案 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
};

我们的想法是创建一个类似于数组的数组,实际上是unionTchar的数组。因此,我们避免实际构建我们的T,同时仍然具有适当的对齐方式。假设char没有非平凡的对齐,则生成的缓冲区与T[]二进制兼容。

然后我们将构造参数emplacer作为对象,它们作为任意构造参数的包。出于恼人的原因,这些对象需要创建一些参数的临时副本(我无法弄清楚如何避免生命周期问题......也许我错过了一些东西)。

my_array的构造函数接受任意数量的emplacers并继续根据其参数构造buff的内容。

你可以创建这样的数组:

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

更多的工作将允许在数组中默认构造未初始化的对象。

但要正确编写这真的非常棘手。