【问题标题】:Unexpected compilation problem with g++ -std=c++0xg++ -std=c++0x 出现意外编译问题
【发布时间】:2011-05-22 19:46:08
【问题描述】:

在使用 g++ -std=c++0x 进行编译时,我遇到了一些将 T 类型的元素推回向量的编译问题。

这是一个最小的例子:

#include <vector>

using namespace std;

class A {
public:
    A() { }

    A& operator=(A &orig) {
        return *this;
    }
};

int main(int argc, char **argv) {
    A a;
    vector<A> b;
    A c = a; // This is fine
    b.push_back(a); // This is not, but only when compiling with -std=c++0x!
    return 0;
}

使用 g++ -Wall -pedantic 可以正常编译,但是使用 g++ -Wall -pedantic -std=c++0x 编译时出现此错误:

 In file included from /usr/include/c++/4.4/vector:69,
                 from min.cpp:1:
/usr/include/c++/4.4/bits/vector.tcc: In member function ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(__gnu_cxx::__normal_iterator<typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer, std::vector<_Tp, _Alloc> >, _Args&& ...) [with _Args = const A&, _Tp = A, _Alloc = std::allocator<A>]’:
/usr/include/c++/4.4/bits/stl_vector.h:741:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const _Tp&) [with _Tp = A, _Alloc = std::allocator<A>]’
min.cpp:20:   instantiated from here
/usr/include/c++/4.4/bits/vector.tcc:314: error: no match for ‘operator=’ in ‘__position.__gnu_cxx::__normal_iterator<_Iterator, _Container>::operator* [with _Iterator = A*, _Container = std::vector<A, std::allocator<A> >]() = ((const A&)((const A*)std::forward [with _Tp = const A&](((const A&)((const A*)__args#0)))))’
min.cpp:11: note: candidates are: A& A::operator=(A&)
In file included from /usr/include/c++/4.4/vector:61,
                 from min.cpp:1:
/usr/include/c++/4.4/bits/stl_algobase.h: In static member function ‘static _BI2 std::__copy_move_backward<true, false, std::random_access_iterator_tag>::__copy_move_b(_BI1, _BI1, _BI2) [with _BI1 = A*, _BI2 = A*]’:
/usr/include/c++/4.4/bits/stl_algobase.h:595:   instantiated from ‘_BI2 std::__copy_move_backward_a(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = A*, _BI2 = A*]’
/usr/include/c++/4.4/bits/stl_algobase.h:605:   instantiated from ‘_BI2 std::__copy_move_backward_a2(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = A*, _BI2 = A*]’
/usr/include/c++/4.4/bits/stl_algobase.h:676:   instantiated from ‘_BI2 std::move_backward(_BI1, _BI1, _BI2) [with _BI1 = A*, _BI2 = A*]’
/usr/include/c++/4.4/bits/vector.tcc:308:   instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(__gnu_cxx::__normal_iterator<typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer, std::vector<_Tp, _Alloc> >, _Args&& ...) [with _Args = const A&, _Tp = A, _Alloc = std::allocator<A>]’
/usr/include/c++/4.4/bits/stl_vector.h:741:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const _Tp&) [with _Tp = A, _Alloc = std::allocator<A>]’
min.cpp:20:   instantiated from here
/usr/include/c++/4.4/bits/stl_algobase.h:561: error: no match for ‘operator=’ in ‘* -- __result = std::move [with _Tp = A&](((A&)(-- __last)))’
min.cpp:11: note: candidates are: A& A::operator=(A&)

所以它似乎没有找到 A 的正确 operator=。为什么?为什么我通过 A 时会显示 with _Iterator = A*

【问题讨论】:

  • 如果您的赋值运算符采用const-reference,它就可以工作,它总是应该这样做的。
  • 如果我将其声明为 A& operator=(A *orig) 也可以,但为什么呢?以及为什么它仍然适用于“普通” g++?
  • A&amp; operator=(A *orig) 是赋值运算符,但不是 copy-assignment 运算符,因此编译器为您隐式定义了默认的复制赋值运算符(就像您根本没有定义赋值运算符)。

标签: c++ c++11


【解决方案1】:

语言标准对标准容器元素强加的 Assignable 要求要求 t = u 表达式有效,即使 u 是 const 对象也是如此。自 C++98(参见 23.1/4)起,该要求就以这种方式定义

您违反了该要求,因为您的赋值运算符不接受 const 对象。这立即意味着您的类 A 不能用作容器元素类型。

为什么它在 C++03 中工作是相当无关紧要的。它意外地起作用了。从错误消息中可以明显看出,库的 C++0x 实现使用了一些 C++0x 特定功能(如std::move),这就是上述要求发挥作用的原因。但无论如何,C++03 实现(甚至 C++98 实现)也可能无法为您的 A 编译。

A c = a; 的示例无关紧要,因为它根本不使用赋值运算符(为什么在这里?)。

为了修复错误,您应该通过 const 引用或值接受参数。

【讨论】:

  • 谢谢。可分配要求仅在 C++0x 中定义,还是仅在使用 -std=c++0x 编译时在 g++ 中强制执行?
  • @akappa:Assignable 要求从 C++98 开始就存在。因此,您的代码通常在任何版本的标准 C++ 中都不可编译。只是在 C++0x 之前的标准的 GCC 实现中,你很幸运,错误就溜走了。但在 C++0x 中,情况不再如此。
  • @akappa : A c = a; 根本不使用赋值,因此在这里无关紧要(它使用复制构造)。 A c; c = a; 是正在讨论的内容。 ;-]
  • @akappa :正确;就像尽可能为您隐式定义复制赋值运算符一样,也尽可能为您隐式定义复制构造函数。 (默认构造函数和析构函数也是如此。)
  • 我相信他也可以使用 Move 可赋值类型。但这需要另外一个移动赋值运算符,而他也没有。
【解决方案2】:

我很确定这是一项安全功能。在标准容器中使用可能会改变右侧的复制赋值运算符(或复制构造函数)的类型安全 - 例如(现已弃用)std::auto_ptr如果存放在容器中会严重损坏。

旧的 C++03 库实现允许此类不安全的代码,但显然他们在 C++0x 版本中实现了编译时检查——可能与移动启用容器结合使用。

【讨论】:

  • 这将完全回答我的问题,尽管选择的错误消息将是 terrible :)
【解决方案3】:

标准对复制赋值运算符的定义是([class.copy] 部分):

用户声明的 copy 赋值运算符X::operator= 是类X 的非静态非模板成员函数,只有一个XX&amp;、@ 类型的参数987654326@、volatile X&amp;const volatile X&amp;

X&amp;volatile X&amp; 变体可能与容器不兼容,假设可以从 r 值 RHS 进行分配。

注意:按值传递,例如X::operator=(X) 是复制和交换习语的基本组成部分。

【讨论】:

  • 感谢您的清晰解释。所以,最后,你同意 JohannesD 关于为什么它在 C++03 中失败而不在 C++0x 中失败的原因?
猜你喜欢
  • 2012-09-29
  • 2015-01-05
  • 1970-01-01
  • 2014-11-05
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
  • 2019-12-24
  • 1970-01-01
相关资源
最近更新 更多