删除默认构造函数会导致 STL 容器中的默认分配器出现问题

Deleting default constructor causes problems with default allocator in STL containers

我有一个 class 看起来像这样:

class PasswordCategory
{
public:
    PasswordCategory(const std::string&);
    ~PasswordCategory();

    PasswordCategory() = delete;
    ...
}

这会导致与分配器相关的编译器错误:

error C2280: 'PasswordCategory::PasswordCategory(void)' : attempting to reference a deleted function
File: xmemory0:577

IDE 是 VS 2013 社区。

我假设发生这种情况是因为我在其他地方使用了这些类别的矢量。

std::vector<PasswordCategory> m_categories;

我只使用 emplace_back(string) 将元素插入其中,但是,似乎 PasswordCategory 的默认分配器正在尝试使用 PasswordCategory 的默认构造函数,但由于它已被删除,它会抛出一个错误。

如果我提供默认构造函数,一切正常,但我想知道如何在没有默认构造函数的情况下缓解这个问题?

我想到了以下解决方案:

  1. 为我的 class 提供自定义分配器来构造我的 class。但是,这并没有解决我希望我的字符串参数是非可选的问题。
  2. 提供默认构造函数,该构造函数仅使用一些参数调用我的其他构造函数。这也没有解决这个参数是非可选的并且不应该被默认的问题。
  3. 使用引用或指针向量而不是值向量。这似乎是最合理的解决方案,但是它需要手动管理我们的内存,除非我们使用 unique_ptr 或类似的东西。

我想知道我是否可以以某种方式禁止 class 的无参数构造,同时仍然能够按值在标准容器中使用它?

感谢任何答案和见解,提前致谢。

P.S。这是我为了更好地理解 C++ 而正在做的一个小项目,我试图在其中避免大多数常见的陷阱并使一切尽可能可靠,这样当我要从事更大的项目时,我会更容易避免这些常见的陷阱。我尝试以不同的方式提出问题,但没有找到我的问题的答案,所以我问自己的问题。

原则上,您不能:标准容器要求包含的对象是默认可构造的。 (请参阅 Mike Seymour 的评论)。

原则上你应该可以,除非你使用内部需要默认构造的操作。

就是说,您可以简单地创建一个空的构造函数(将成员默认为 sane/calls 另一个带有一些参数的构造函数)。如果您正确编写客户端代码,您将不会遇到使用默认值初始化的对象。

需要默认构造对象的操作通常是调整大小,还有一些其他需要创建内部对象的操作(即,除非您想保留一个元素并在不显式初始化它的情况下使用它,否则您应该没有问题) .

std::vector 本身并不要求 TDefaultConstructible 类型:

直到 C++ 11:

T must meet the requirements of CopyAssignable and CopyConstructible.

自 C++ 11 起:

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type is a complete type and meets the requirements of Erasable, but many member functions impose stricter requirements.

详情见this page

但是,您可能会在容器上执行涉及创建隐式实例的操作,这就是您收到此错误的原因。如果您可以跟踪并消除它们,一切都应该正常,因为如果不使用默认构造函数,则根本不需要它。

正在考虑您的提议:

1. 为构建我的 class 的 class 提供自定义分配器。

这无济于事 - std::allocator 不负责默认构造元素,因为它根本没有定义此类功能。参见 std::allocator::construct

编辑 这里的小错误,我没有注意到 C++ 11 中的小变化:

直到 C++ 11

void construct( pointer p, const_reference val );

从 C++ 11 开始

template< class U, class... Args >
void construct( U* p, Args&&... args );

2. 提供默认构造函数,该构造函数仅使用一些参数调用我的其他构造函数。

这也没有解决这个参数是非可选的并且不应该被默认的问题。 这也不是完全便携的。某些编译器(如 VC11)不支持委派构造函数。

3. 使用引用或指针向量而不是值向量。

这似乎是最合理的解决方案,但是它引入了手动管理内存的需要,除非我们使用 unique_ptr 或类似的东西。 不太有效 - 您无法创建引用容器。最接近的解决方案是容纳 std::reference_wrappers 的容器。 raw/smart 指针的容器也是一个选项,但这就​​是事情开始变得混乱的地方。

此外,在您的原始代码中,无需声明已删除的默认构造函数 - 如果您声明 any 构造函数,则意味着没有默认构造函数(除非您定义它)和编译器不会生成任何。