【问题标题】:Deleting default constructor causes problems with default allocator in STL containers删除默认构造函数会导致 STL 容器中的默认分配器出现问题
【发布时间】:2015-03-30 12:40:39
【问题描述】:

我有一个如下所示的课程:

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

    PasswordCategory() = delete;
    ...
}

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

错误 C2280:'PasswordCategory::PasswordCategory(void)':正在尝试 引用已删除的函数
文件:xmemory0:577

IDE 是 VS 2013 社区。​​p>

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

std::vector<PasswordCategory> m_categories;

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

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

我想到了以下解决方案:

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

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

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

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

【问题讨论】:

  • 你能告诉我们你对向量做了什么导致错误吗?如果实现符合 C++11/14,它应该可以工作,只要您不做任何特别需要默认构造函数的事情(例如,使用单个参数调用 resize)。定义一个空向量,并使用适当的构造函数参数调用emplace_back,应该没问题。
  • 啊,这很好奇。在阅读了您的评论后,我实际上设法找到了罪魁祸首。我正在使用 0 参数初始化我的向量:PasswordManager::PasswordManager() : m_categories(0) {} 在使其成为 m_categories() 之后,代码编译。我认为初始化大小为 0 的向量不会导致默认分配器的实例化。事实上,我认为它会更好,因为它更冗长,并且清楚地表明向量开始时​​是空的。原来我错了。
  • 您告诉向量使用 N 个默认构造元素填充自身,这采用需要您的类型的默认构造函数的代码路径(即使 N 的运行时值为 0)。默认构造你说你希望它为空的向量,这不需要你的类型的任何构造函数。
  • 确实,该构造函数包含用于默认初始化元素的代码 - 因为这是它指定要做的事情 - 所以无论该代码是否实际执行都是错误的。
  • 注:您实际上不需要删除默认构造函数,PasswordCategory(const std::string&amp;) 构造函数的存在意味着无论如何都没有默认构造函数。

标签: c++ oop constructor stl allocator


【解决方案1】:

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

直到 C++ 11:

T 必须满足 CopyAssignable 和 CopyConstructible 的要求。

从 C++ 11 开始:

对元素的要求取决于对容器执行的实际操作。一般要求元素类型是完整类型,满足Erasable的要求,但很多成员函数要求更严格。

详情请见this page

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

考虑您的建议:

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

这无济于事 - 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。原始/智能指针的容器也是一种选择,但这就是事情开始变得混乱的关键。

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

【讨论】:

  • 在 C++11 中 std::allocator::construct(T*) 将用于默认构造的元素
  • @JonathanWakely 是的,你是对的,我忘记了在 std::allocator 中使用 C++ 11 所做的更改。答案已更新,感谢您的评论。
【解决方案2】:

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

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

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

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

【讨论】:

  • “标准容器要求包含的对象是默认可构造的”——不,它们不需要,因为 C++11。只有少数指定的操作需要它。
猜你喜欢
  • 2013-08-19
  • 2010-09-11
  • 2014-08-22
  • 1970-01-01
  • 2022-01-04
  • 1970-01-01
  • 2019-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多