std ::用G ++交换怪异

时间:2013-06-27 14:34:38

标签: c++ compiler-errors std swap

这是一个奇怪的地方,我不知道,如果是C ++标准,我的编译器(Ubuntu 12.04上的G ++版本4.6.3,这是Ubuntu的最新长期支持版本)或者我,谁不明白; - )

有问题的代码简单如下:

#include <algorithm>    // for std::swap
void f(void)
{
    class MyClass { };
    MyClass aa, bb;
    std::swap(aa, bb);          // doesn't compile
}

尝试使用G ++编译时,编译器会产生以下错误消息:

test.cpp: In function ‘void f()’:
test.cpp:6:21: error: no matching function for call to ‘swap(f()::MyClass&, f()::MyClass&)’
test.cpp:6:21: note: candidates are:
/usr/include/c++/4.6/bits/move.h:122:5: note: template<class _Tp> void std::swap(_Tp&, _Tp&)
/usr/include/c++/4.6/bits/move.h:136:5: note: template<class _Tp, long unsigned int _Nm> void std::swap(_Tp (&)[_Nm], _Tp (&)[_Nm])

令人惊讶的结果是,只是将类定义移出函数使得代码编译正常:

#include <algorithm>    // for std::swap
class MyClass { };
void f(void)
{
    MyClass aa, bb;
    std::swap(aa, bb);          // compiles fine!
}

那么,std :: swap()不适用于对函数而言是私有的类吗?或者这是G ++的一个错误,也许是我正在使用的G ++的特定版本?

更令人费解的是,尽管MyListClass也是私有的(但是扩展了一个“官方”类,其中可能存在swap()的特定实现),以下内容再次起作用:

#include <algorithm>        // for std::swap
#include <list>             // for std::list
void g(void)
{
    class MyListClass : public std::list<int> { };
    MyListClass aa, bb;
    std::swap(aa, bb);              // compiles fine!
}

但只是从对象更改为指针,编译再次失败:

#include <algorithm>        // for std::swap
#include <list>             // for std::list
void g(void)
{
    class MyListClass : public std::list<int> { };
    MyListClass aa, bb;
    MyListClass* aap = &aa;
    MyListClass* bbp = &bb;
    std::swap(aap, bbp);    // doesn't compile!
}

当然,在我的实际应用中,课程更复杂;我尽可能地简化了代码,以便仍能重现问题。

1 个答案:

答案 0 :(得分:16)

如果您在C ++ 03模式下运行(我认为是这种情况),则不允许在模板中使用本地定义的类型。如果是这种情况,您可以在命名空间级别定义类型以使其工作,否则您可以在C ++ 11模式下编译它应该编译。[*]

如果您想知道为什么第二种情况有效,则该标准不提供

的特化
template <typename T> void swap(T&,T&) // [1] 

因为std::list本身就是一个模板,你不能部分专门化模板功能。它提供的是一个不同的基本模板:

template <typename T, typename A> void swap(list<T,A>&,list<T,A>&); // [2]

现在和前面的情况一样,编译器不能将本地类型与[1]一起使用,因此会被丢弃。然后它尝试[2],它发现它可以将本地类型的左值转换为对基类std::list<int>的引用,之后转换[2]是一个很好的候选者。然后它会调用

std::swap(static_cast<std::list<int&>>(aa),static_cast<std::list<int&>>(bb));

不使用本地类型,而是使用名称空间级别std::list<int>

另一方面,它编译的事实并不意味着它能做到你想要的。特别是,如果扩展类型MyListClass添加了任何新成员变量,那么将被交换。

所有这些,并且只是作为旁注:你不应该继承标准容器,因为它们从未被设计为继承。

[*]免责声明:我不知道该特定版本的编译器是否支持此功能,您必须仔细检查。