我可以可靠地emplace_back没有赋值运算符的类型的向量吗?

Can I reliably emplace_back in a vector of a type that does not have an assignment operator?

提问人:really 提问时间:12/23/2022 更新时间:12/24/2022 访问量:80

问:

我在 GCC、Clang 和 MSVC 中进行了一些测试,发现从不对包含的类调用赋值运算符。它仅在发生重新分配时调用 copy 或 move 构造函数。这种行为是否以某种方式得到标准的保证?emplace_back

用例是,我有一些类要按顺序存储在一个数字中,该数字只会随着时间的推移而增长,直到整个向量被破坏。我很乐意从赋值运算符中清理我的代码。

C++ 向量 分配 move-constructor move-assignment-operator

评论

0赞 Jesper Juhl 12/23/2022
找出答案的一种简单方法是尝试 - 删除赋值运算符以及代码是否仍在编译;你是金色的;-)
1赞 really 12/23/2022
@Jesper Juhl 中,它确实可以编译,但我认为它不仅会在某些实现实际调用赋值运算符时才会编译
4赞 Serge Ballesta 12/23/2022
@JesperJuhl:OP询问它是否得到标准的保证。在特定编译器的特定版本上工作并不完全是一回事......

答:

1赞 Lorah Attkins 12/23/2022 #1

是的,emplace back 将调用移动构造函数,前提是有一个可用(或就地构造):

#include <iostream>
#include <vector> 

struct Pt {
    Pt() = default;
    
    Pt& operator=(Pt const& other) = delete;
    
    Pt(Pt const&) { std::cout << "copy ctor" << std::endl; }
    Pt(Pt&&){ std::cout << "move ctor" << std::endl; }
};

int main(int argc, char *argv[]) 
{
    std::vector<Pt> v;
    
    v.emplace_back(Pt{});
   
    return 0;  
}  

演示

应用于该类型的实际约束是使其为 MoveInsertableEmplaceConstructible

评论

0赞 really 12/23/2022
谢谢。我做过类似的测试。将尝试了解这些约束是否意味着赋值运算符。
1赞 Lorah Attkins 12/23/2022
@really 不,他们没有。与赋值相关的约束被命名为“..可分配”。当其他东西不存在时,赋值运算符可以是一个回退,所以所有这些约束都说你的类型必须提供一组其他属性
5赞 François Andrieux 12/23/2022 #2

这些要求记录在 cppreference.com。

对于 std::vector<T>::emplace_back

型号要求

-T(容器的类型)必须满足 MoveInsertableEmplaceConstructible 的要求。element

此外,还有一个一般要求,即类型必须是可擦除的。std::vector

EmplaceConstructible 要求使用给定的分配器使用提供的参数来构造类型。由于您使用的是 默认分配器 ,这仅意味着该类型具有具有这些参数的可访问构造函数。std::allocator<T>

MoveInsertable 要求该类型可使用给定的分配器构造,使用 类型 的右值。因为这意味着该类型具有可访问的移动构造函数。Tstd::allocator

可擦除要求使用给定的分配器可破坏类型。因为这意味着它有一个可访问的析构函数。std::allocator

就是这样(除了一些关于完整类型与不完整类型的规则)。没有提及赋值运算符的要求。

但在 C++11 之前,类型总是必须是 CopyAssignableCopyConstructible。此后,这种情况有所放松。现在,除了 Erasable 之外,要求仅取决于对向量执行的操作。

评论

0赞 asu 12/23/2022
emplace_back无论如何,移动语义都是 C++11,所以关于旧 C++ 标准的观点不是有些无关紧要吗?
1赞 François Andrieux 12/24/2022
@Asu我认为你的结论是正确的,我会删除它。虽然我觉得提到旧的要求可以帮助人们理解需要赋值运算符的印象可能来自哪里。因为它曾经是真的。
1赞 asu 12/23/2022 #3

这是cpreference所说的emplace_back

  • T(容器的元素类型)必须满足 MoveInsertableEmplaceConstructible 的要求。

对于 EmplaceConstructible,要求是 to be valid。这是无聊的约束,仅对实际放置元素有用。std::allocator_traits<A>::construct(m, p, args);

MoveInsertable 似乎是重新分配所需的概念。
对于 MoveInsertable,要求是 to be valid,其中 is type (即您的类型) 的右值表达式。
std::allocator_traits<A>::construct(m, p, rv);rvT

构造的定义有点令人困惑,但对于大多数意图和目的,它似乎只是将参数转发给构造函数。

换句话说,这两个概念似乎证实了唯一的约束是您需要提供:emplace_back

  • 允许您就地构造的实际构造函数 (DUH)
  • 重新分配时用于重定位的移动构造函数(如果缺少,则为复制)