【问题标题】:Array initialization of objects without operator =, copy constructor or default constructor and run-time arguments没有运算符 =、复制构造函数或默认构造函数和运行时参数的对象的数组初始化
【发布时间】:2014-03-03 05:05:28
【问题描述】:

免责声明

我正在尝试分配一个对象数组,这些对象既不能复制构造,也不能赋值,也没有默认构造函数。对象具有在运行时确定的参数。我知道您可以通过使用指针数组或巧妙地使用placement new 来解决这个问题,但我更感兴趣的是这是否可以用C++11 (1y) 魔术干净地完成。 所以,这纯粹是理论上的兴趣,所以请避免尝试通过提供解决方法来解决“我的问题”。

代码...

...所以问题是:有没有办法在 C++11 或 C++14 中进行以下工作:

class X{
public:
  explicit X(int a){...}
  X(const X&) = delete;
  void operator = (const X&) = delete;
private:
  ...
};

class Y{
public:
  Y(const std::vector<int>& args) {
    x = new X[]{args};
  }
  ~Y(){
    delete [] x;
  }
private:
  X* x;
};

标准

具体来说,我正在寻找满足以下条件的解决方案/构造:

  • X 不可复制。
  • X 不可分配。
  • X 没有默认的无参数构造函数(构造具有预期的副作用)。
  • X 的构造函数的参数直到运行时才知道。
  • X 的所有实例必须在内存中连续布局。
  • 当数组从它的基指针中删除时(或者如果使用中间类,当中间对象被破坏时),X 必须被正确地破坏。这排除了指针数组和对放置 new 的天真使用。

编辑/附录

我忘了提到移动构造函数不可用。在手头的实际情况下,X 产生一个工作线程并在初始构造对象的this 上下文中执行,任何使用移动构造函数的尝试都会破坏执行线程的状态。

【问题讨论】:

  • 使用vectoremplace_back
  • @Fernandes 回复:编辑。我专门使用术语“C++1x”来表示 C++11 或 C++14 中的答案。请看:meta.stackexchange.com/questions/11474/…这既不是语法或拼写错误,也不是澄清(你改变了我的意思)。
  • 那么请使用“C++11 或 C++14”。 “C++1x”曾用于表示“即将发布的 C++ 版本,过去称为 C++0x,但现在将在第三个千年的第二个十年发布”。这无助于清晰。
  • (a) 编辑修复了您的问题的一个明显问题。整个 C++ 社区,包括标准工作组,都使用 C++1y 来表示 C++14。它绝不意味着 C++11。 C++1x 根本不存在,但它存在的地方,由于 C++0x 的历史,它是模棱两可的。因此,在马蒂尼奥非常友好地为您解决之前,您的意图被歪曲了。 “谢谢”是合适的。 (b) 这不是您的帖子;这是一个问答数据库,而不是您的个人编程帮助台。
  • (c) 无需同意:您通过在 CC By-SA 3.0 许可下发布并阅读解释本网站如何运作的所有常见问题解答明确放弃了所有声明。希望能减轻你的一些困惑。另外,(d) 不要这么粗鲁。

标签: c++ arrays c++11 initialization c++14


【解决方案1】:

如果您使X 至少可移动,则可以使用std::vector 及其emplace_back 函数。

class X{
public:
  explicit X(int){}
  X(X&&) = default;
  X(const X&) = delete;
  void operator = (const X&) = delete;
};

int main() {
    std::vector<X> xs;
    xs.emplace_back(0);
    xs.emplace_back(1);
    xs.emplace_back(2);
    xs.emplace_back(3);
}

(如果你声明了一个拷贝构造函数,即使那个声明删除了它,编译器也不会自动生成任何特殊的移动成员,所以你需要显式地请求它们)

这基本上归结为“新放置的数组”策略,但都抽象为高级概念。

如果您不能使用可移动类型,则必须实现一个类似矢量的类,该类预先分配存储并且从不重新分配。标准库中没有类似的东西。

【讨论】:

  • 我知道我忘记了一些事情,由于构造函数中的副作用(在this 的上下文中启动执行代码的工作线程),我无法使用移动构造函数。在帖子中添加了说明。
  • FWIW,移动构造函数仅用于编译。如果您事先致电reserve,它将不会被使用。如果你真的想避免它,你将不得不编写一个永远不会重新分配的类向量类。
  • 然后我冒着在某个愚蠢的时刻无意中使用该移动构造函数的风险。 ;\ 当然我可以在编译时抛出并捕获它,但我不喜欢那样。
  • 必须this作为上下文,不能用指向结构的成员指针作为上下文,然后在移动构造函数中实际可以移动吗?
【解决方案2】:

既不能复制也不能移动,也没有默认构造函数的类,不能保存在标准容器(不满足要求)或可变大小的数组分配(只允许指定固定数字的参数元素)。

这意味着您需要改为分配原始内存并使用placement new 来构造对象。您可以将其包装在一个固定空间矢量类中。

template <typename T>
class fixed_capacity_vector {
public:
  using size_type = std::size_t;

  fixed_capacity_vector(size_type capacity)
    : data_(::operator new(capacity * sizeof(T)), size_(), capacity_(capacity)
  {}

  fixed_capacity_vector(const fixed_capacity_vector&) = delete;
  fixed_capacity_vector(fixed_capacity_vector&&) = delete;
  fixed_capacity_vector& operator =(const fixed_capacity_vector&) = delete;
  fixed_capacity_vector& operator =(fixed_capacity_vector&&) = delete;

  ~fixed_capacity_vector() {
    for (size_type i = 0; i < size_; ++i) data_[i].~T();
    ::operator delete(data_);
  }

  template <typename... Args>
  T& emplace_back(Args&&... args) {
    if (size_ == capacity_) throw out_of_range();
    new (data_ + size_) T(std::forward<Args>(args)...);
    ++size_;
    return data_[size_-1];
  }

private:
  T* data_;
  size_type size_;
  size_type capacity_;
};

【讨论】:

  • 我建议从最后到第一个进行销毁,因为在语言和标准库中,销毁通常以与构建相反的顺序执行(std::vector 也这样做)。不过,请注意递减循环中的无符号类型。
  • 是的,未签名的事情是我转发的原因。在实际的实现中,我会有迭代器和反向迭代器,我会使用它们。
  • 没有 contiguous 标准容器可以存储不可复制、不可移动的非默认构造类型。放宽连续要求,可以使用双端队列或列表。
  • while(size_) data[--size_].~T(); 有什么问题?
【解决方案3】:

您将不得不手动跟踪构造的元素,但您可以使用allocator 来帮助:

class Y{
public:
  Y(const std::vector<int>& args):
    alloc{},
    n{args.size()},
    x{alloc.allocate(n)}
  {
    auto end = x;
    try {
      for (auto arg: args)
        alloc.construct(end++, arg);
    } catch (...) {
      while (end != x)
        alloc.destroy(--end);
      alloc.deallocate(x, n);
      throw;
    }
  }
  ~Y() {
    for (auto end = std::next(x, n); end != x; --end)
      alloc.destroy(end);
    alloc.deallocate(x, n);
  }
private:
  std::allocator<X> alloc;
  const std::size_t n;
  const X *x;
};

【讨论】:

  • 我会从std:: allocator 私下继承以利用空基优化。并且可能参数化X 和分配器类型,因为一切都应该是一个模板;)
  • 另外,没有必要同时存储大小和结束指针,因为整个范围总是在构造函数中初始化。任何一个都足够了。
  • @Casey 如果Xs 之一抛出构造,我们只需要销毁之前的元素。如果X 不能从int 构造,那么,是的,我们可以消除结束指针。
  • 如果Xs 之一抛出,构造将不会完成,因此析构函数将不会运行。您需要 try/catch/throw 和手动清理或封装在另一个仅处理释放/销毁的类中。
  • @Casey 当然!固定的。这就是为什么编写异常安全的容器困难
猜你喜欢
  • 2011-06-12
  • 1970-01-01
  • 1970-01-01
  • 2013-03-16
  • 1970-01-01
  • 1970-01-01
  • 2021-12-16
  • 1970-01-01
相关资源
最近更新 更多