C++ 标准禁止在命名空间中声明类型或定义任何内容std
,但它允许您为用户定义的类型专门化标准 STL 模板。
通常,当我想专注std::swap
于我自己的自定义模板类型时,我只是这样做:
namespace std
{
template <class T>
void swap(MyType<T>& t1, MyType<T>& t2)
{
t1.swap(t2);
}
}
...效果很好。但我不完全确定我的惯常做法是否符合标准。我这样做正确吗?
您所拥有的不是专业化,而是重载,并且正是标准所禁止的。(但是,它目前在实践中几乎总是有效的,并且您可能可以接受。)
以下是你如何为你的类模板提供你自己的交换:
template<class T>
struct Ex {
friend void swap(Ex& a, Ex& b) {
using std::swap;
swap(a.n, b.n);
}
T n;
}
这就是你如何调用交换,你会注意到它也在 Ex 的交换中使用:
void f() {
using std::swap; // std::swap is the default or fallback
Ex<int> a, b;
swap(a, b); // invokes ADL
}
为什么不直接在 MyType 的命名空间中定义 swap 并利用依赖于参数的查找功能?
由于参数依赖(又名 Koenig)查找,我相信您可以在所需类型的命名空间中指定自己的交换,并且它会优先于::std::swap
. 另外,我相信对于具有自己的交换成员函数的类,模板的::std::swap
扩展方式会有所不同,因此您可以将该成员函数添加到类中,并将用于您的类型。
请参阅 Scott Meyer 的文章:See Effective C++ 3rd Edition,第 25 项:考虑支持非抛出交换 (p106-p112)以确认我的答案。
斯科特迈耶斯写过这个,所以我的回答来自记忆。
首先,在类的命名空间中定义一个交换函数。例如 :
namespace MyNamespace
{
class MyClass { /* etc. */ } ;
template<typename T>
class MyTemplate { /* etc. */ } ;
void swap(MyClass & lhs, MyClass & rhs)
{
// the swapping code (**)
}
template<typename T>
void swap(MyTemplate<T> & lhs, MyTemplate<T> & rhs)
{
// the swapping code (**)
}
}
然后,如果可能(模板类 (*) 并不总是可能),在命名空间 std 中专门化交换函数。例如 :
namespace std
{
template<>
void swap<MyNamespace::MyClass>(MyNamespace::MyClass & lhs, MyNamespace::MyClass & rhs)
{
// the swapping code (**)
}
// The similar code for MyTemplate is forbidden, so don't try
// to uncomment it
//
// template<typename T>
// void swap<MyNamespace::MyTemplate<T> >(MyNamespace::MyTemplate<T> & lhs, MyNamespace::MyTemplate<T> & rhs)
// {
// // the swapping code (**)
// }
}
在使用交换功能时,间接执行,将标准交换功能导入您的范围。例如 :
void doSomething(MyClass & lhs, MyClass & rhs)
{
// etc.
// I swap the two objects below:
{
using std::swap ;
swap(lhs, rhs) ;
}
// etc.
}
void doSomethingElse(MyTemplate<int> & lhs, MyTemplate<int> & rhs)
{
// etc.
// I swap the two objects below:
{
using std::swap ;
swap(lhs, rhs) ;
}
// etc.
}
一旦我可以访问我的书籍,我将在此处发布确切的参考。
您正在做的是重载而不是模板专业化。该标准不允许您在内部超载namespace std
(17.6.4.2.1 §1)
除非另有说明,否则如果 C++ 程序
namespace std
在命名空间中添加声明或定义,则其行为未定义。只有当声明依赖于用户定义的类型并且特化满足原始模板的标准库要求并且没有明确禁止时,namespace std
程序才能为任何标准库模板添加模板特化。namespace std
因此,更愿意将您的模板类型放入您自己的命名空间中并在该命名空间中定义一个非成员swap()
(这不是绝对必要的,但很好的做法)。如果或在您的命名空间中,这种方式swap(x,y)
将通过参数相关查找(ADL,又名 Koenig 查找)在任何地方工作。x
y
namespace my_ns {
template <typename T> class MyType
{
public:
void swap( MyType & other ) noexcept;
};
template <typename T>
void swap( MyType<T> & lhs, MyType<T> & rhs ) noexcept
{
lhs.swap(rhs);
}
} // namespace my_ns
使用的代码swap()
通常应该使用该using namespace std
技术。这样,ADL 将找到您的交换版本,并且它将优先于std::swap()
函数,因为它更专业。
// client code
MyType<Bla> x, y;
/* ... some code ... */
using namespace std;
swap( x, y ); // will call your swap version
在同一个命名空间中定义您的类型和交换函数:
namespace foo
{
struct Bar
{
};
void swap(Bar & t1, Bar& t2)
{
// whatever
}
}
int main()
{
using std::swap;
foo::Bar a, b;
swap(a, b); // Argument-dependent lookup chooses foo::swap
// if it exists, or else reverts to std::swap
}
定义自己的swap
. 此函数必须为除您的类型之外的任何类型 T 调用 std::swap 。
namespace help // my namespace
{
template <class T>
void swap(T& t1, T& t2)
{
::std::swap(t1, t2); // Redirect to std for almost all cases
}
// My special case: overloading
template <class T>
void swap(MyType<T>& t1, MyType<T>& t2)
{
t1.swap(t2);
}
} // namespace help
// Sample
int main()
{
MyType<int> t1, t2; // may be add initialization
int i1=5, i2=7;
help::swap(t1, t2); // Your swap
help::swap(i1, i2); // Redirect to std::swap
}