std ::用G ++交换怪异

Kai*_*zke 10 c++ swap compiler-errors std

这是一个奇怪的,我不知道,如果它是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
}
Run Code Online (Sandbox Code Playgroud)

尝试使用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])
Run Code Online (Sandbox Code Playgroud)

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

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

那么,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!
}
Run Code Online (Sandbox Code Playgroud)

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

#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!
}
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*eas 16

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

如果你想知道为什么第二种情况有效,标准不提供专业化

template <typename T> void swap(T&,T&) // [1] 
Run Code Online (Sandbox Code Playgroud)

作为std::list模板本身,您不能部分专门化模板功能.它提供的是一个不同的基本模板:

template <typename T, typename A> void swap(list<T,A>&,list<T,A>&); // [2]
Run Code Online (Sandbox Code Playgroud)

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

std::swap(static_cast<std::list<int&>>(aa),static_cast<std::list<int&>>(bb));
Run Code Online (Sandbox Code Playgroud)

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

另一方面,它编译的事实并不意味着它做你想要的.特别是,如果扩展类型MyListClass添加任何新成员变量,则不会交换这些变量.

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

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