1

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

4 回答 4

4

因为各个数组元素是通过 =语法指定的初始化程序的复制初始化来初始化的。{...}见 8.5/12 (C++03)

在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和大括号括起来的初始化列表 (8.5.1) 中发生的初始化称为复制初始化

复制初始化需要复制构造函数(即使它实际上不会使用它)。

实际上,如果您通过公开复制构造函数来编译代码,编译器最终可能会在不使用复制构造函数的情况下初始化您的数组元素。然而,抽象语言的正式规则要求在这种情况下进行复制初始化。

于 2013-01-27T01:43:52.757 回答
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()
{}
于 2013-01-27T01:55:05.183 回答
1

假设 的复制构造函数Irritating被禁用,因为它比通过引用管理它们可能最好:

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

您可以使用shared_ptr而不是unique_ptr取决于使用模式。

(如果你可以修改 Irrating 你可以给它一个移动构造函数,看看移动语义)

如果您真的希望它们就地构建,那么您可以使用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(...);

(不要忘记在程序退出时放置删除它们。)

于 2013-01-27T02:01:27.947 回答
-4

尝试只创建一个大小的数组?应该调用默认 ctor。

正如在这个来源

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

我们的想法是我们创建一个类似数组的构造,它实际上是一个uniontoT和 a的数组char。因此,我们避免实际构建我们的T, 同时仍然具有适当的对齐方式等。假设char没有非平凡对齐,则生成的缓冲区与T[].

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

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

你会创建你的数组是这样的:

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

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

但这真的,真的很难正确地写出来。

于 2013-01-27T01:41:04.820 回答