为什么std::sort会抱怨删除的复制构造函数?

3
假设我们有一个简单的类,其中包含一个 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;
};

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

现在我们尝试对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

具体而言,它表明std::sort试图引用被删除的复制构造函数,但我不知道为什么会这样;我希望std::sort只使用移动操作。
有人能否解释一下这里发生了什么?

4
所有的错误信息都涉及到 operator=。当一个类有 const 成员时,它不会生成赋值运算符。 - Pete Becker
当然,我之前评论中的“The won't”应该是“编译器不会”。 - Pete Becker
2
= default:在这种情况下,默认的移动运算符也被删除。 - Mooing Duck
StringWrapper 对象不可排序,因为它们不可分配和移动分配。您需要提供一个可移动分配的实现。 - Eljay
请注意,您还需要实际的比较手段,无论是 operator< 还是另一个函数。 - Justin Time - Reinstate Monica
@Pete Becker和@Mooing Duck - 谢谢!我的IDE因某种原因突出了我的复制构造函数,但我现在看到它实际上是在抱怨赋值运算符,并且由于const成员默认被删除,这是有道理的。 - Dan
1个回答

3

这个问题在评论中已经有所涉及,但值得记下的是,使用default关键字并不保证编译器总是会尝试创建特殊成员函数的默认实现。就像通常一样,在cppreference上关于移动赋值运算符的章节中对此进行了很好的解释。

如果类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" 关键字允许您绕过语言限制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接