为什么 std::sort 抱怨已删除的副本 ctor?

Why is std::sort complaining about a deleted copy ctor?

提问人:Dan 提问时间:7/13/2021 更新时间:7/13/2021 访问量:353

问:

假设我们有一个简单的类,它包含一个:std::string

class StringWrapper {
public:
    const std::string s;

    StringWrapper(const std::string s)
        : s(s) {}

    // We want this to be moveable but not copyable
    ~StringWrapper() = default;
    StringWrapper(const StringWrapper&) = delete;
    StringWrapper& operator=(const StringWrapper&) = delete;
    StringWrapper(StringWrapper&&) noexcept = default;
    StringWrapper& operator=(StringWrapper&&) = default;
};

在这里,我们试图使类可移动,但不可复制。我知道成员变量通常会阻止生成默认的移动 ctor,但在这里我们尝试显式生成它 - 这至少可以编译。const

现在我们尝试对 a 进行排序(比较函数在这里并不重要):std::vector<StringWrapper>

std::vector<StringWrapper> strings;
std::sort(strings.begin(), strings.end(), [](auto const& a, auto const& b) { return true; });

这无法编译,但错误非常隐晦:

1>...\include\algorithm(7419,25): error C2280: 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': attempting to reference a deleted function
1>...src\example.cpp(476): message : see declaration of 'StringWrapper::operator ='
1>...src\example.cpp(476,24): message : 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': function was explicitly deleted
1>...\include\algorithm(7541): message : see reference to function template instantiation '_BidIt std::_Insertion_sort_unchecked<_RanIt,_Pr>(const _BidIt,const _BidIt,_Pr)' being compiled
1>        with
1>        [
1>            _BidIt=StringWrapper *,
1>            _RanIt=StringWrapper *,
1>            _Pr=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>
1>        ]
1>...\include\algorithm(7571): message : see reference to function template instantiation 'void std::_Sort_unchecked<StringWrapper*,_Fn>(_RanIt,_RanIt,int,_Pr)' being compiled
1>        with
1>        [
1>            _Fn=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>,
1>            _RanIt=StringWrapper *,
1>            _Pr=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>
1>        ]
1>...src\example.cpp(488): message : see reference to function template instantiation 'void std::sort<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<_Ty>>>,myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>>(const _RanIt,const _RanIt,_Pr)' being compiled
1>        with
1>        [
1>            _Ty=StringWrapper,
1>            _RanIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<StringWrapper>>>,
1>            _Pr=myFunc::<lambda_8668e50965d967f7b587b72f59fcb0cf>
1>        ]
1>...\include\algorithm(7422,28): error C2280: 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': attempting to reference a deleted function
1>...src\example.cpp(476): message : see declaration of 'StringWrapper::operator ='
1>...src\example.cpp(476,24): message : 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': function was explicitly deleted
1>...\include\algorithm(7425,24): error C2280: 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': attempting to reference a deleted function
1>...src\example.cpp(476): message : see declaration of 'StringWrapper::operator ='
1>...src\example.cpp(476,24): message : 'StringWrapper &StringWrapper::operator =(const StringWrapper &)': function was explicitly deleted

具体来说,它表明正在尝试引用已删除的副本 ctor,但我不知道为什么会这样;我希望只使用移动操作。std::sortstd::sort

谁能向我解释一下这是怎么回事?

C++

评论

4赞 Pete Becker 7/13/2021
所有错误消息都参考 。当类具有成员时,不会生成赋值运算符。operator=const
0赞 Pete Becker 7/13/2021
当然,我之前评论中的“不会”应该是“编译器不会”。
2赞 Mooing Duck 7/13/2021
= default:在这种情况下,默认的移动运算符也会被删除。
0赞 Eljay 7/13/2021
StringWrapper对象不可排序,因为它们不可赋值,也不可移动赋值。您需要提供可移动的实现。
0赞 Justin Time - Reinstate Monica 7/13/2021
请注意,您还需要一种实际的比较方法,无论是其他函数还是其他函数。operator<

答:

3赞 Jon Reeves 7/13/2021 #1

这在注释中已经介绍过了,但值得注意的是,对于后代来说,使用关键字并不能保证编译器总是会尝试创建特殊成员的默认实现。这(像往常一样)在 cpppreference 上的移动赋值运算符一节中得到了很好的解释。default

如果满足以下任一条件,则类 T 的隐式声明或默认移动赋值运算符定义为已删除:

  • T 有一个非静态数据成员,即 。const

编译器也会像这里一样抱怨这种情况。例如 clang:

warning: explicitly defaulted move assignment operator is implicitly deleted [-Wdefaulted-function-deleted]
    StringWrapper& operator=(StringWrapper&&) = default;
                   ^
note: move assignment operator of 'StringWrapper' is implicitly deleted because field 's' has no move assignment operator
    const std::string s;

请注意,这是一个警告,而不是一个错误,这就是你最终遇到这种情况的方式。一般来说,最好不要假设关键字允许您绕过语言限制。default