std::swap()
在排序甚至分配期间,许多std容器(例如std::list
和std::vector
)都使用它。
但是std的实现swap()
非常笼统,对于自定义类型而言效率很低。
因此,可以通过std::swap()
使用自定义类型特定的实现进行重载来获得效率。但是如何实现它,以便将其用于std容器?
std::swap()
在排序甚至分配期间,许多std容器(例如std::list
和std::vector
)都使用它。
但是std的实现swap()
非常笼统,对于自定义类型而言效率很低。
因此,可以通过std::swap()
使用自定义类型特定的实现进行重载来获得效率。但是如何实现它,以便将其用于std容器?
Answers:
重载交换的正确方法是将其写入与交换内容相同的名称空间中,以便可以通过依赖于参数的查找(ADL)找到它。一件特别容易做的事是:
class X
{
// ...
friend void swap(X& a, X& b)
{
using std::swap; // bring in swap for built-in types
swap(a.base1, b.base1);
swap(a.base2, b.base2);
// ...
swap(a.member1, b.member1);
swap(a.member2, b.member2);
// ...
}
};
std::sort
使用ADL交换元素的A 不符合C ++ 03,但符合C ++ 11。另外,为什么要基于客户端可能使用非惯用代码的事实来为答案-1呢?
注意Mozza314
这是通用std::algorithm
调用效果的模拟std::swap
,并让用户在命名空间std中提供其交换。由于这是一个实验,因此此模拟使用namespace exp
代替namespace std
。
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
exp::swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
namespace exp
{
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
对我来说,它打印出来:
generic exp::swap
如果您的编译器打印出不同的内容,则说明它没有正确实现模板的“两阶段查找”。
如果您的编译器符合(符合C ++ 98/03/11中的任何一种),那么它将提供与我所示相同的输出。在这种情况下,确实会发生您担心的事情。并且将您swap
放入命名空间std
(exp
)并不能阻止它的发生。
Dave和我都是委员会成员,并且已经在该标准的这一领域工作了十年(彼此之间并不总是一致的)。但是,这个问题已经解决了很长时间,我们都同意如何解决。忽视Dave在这方面的专家意见/答案,后果自负。
C ++ 98发布后,这个问题就暴露出来了。从2001年Dave开始,我开始在这一领域工作。这是现代的解决方案:
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
void swap(A&, A&)
{
printf("swap(A, A)\n");
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
输出为:
swap(A, A)
更新资料
观察到:
namespace exp
{
template <>
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
作品!那么为什么不使用它呢?
考虑您A
是类模板的情况:
// simulate user code which includes <algorithm>
template <class T>
struct A
{
};
namespace exp
{
template <class T>
void swap(A<T>&, A<T>&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A<int> a[2];
exp::algorithm(a, a+2);
}
现在,它不再起作用。:-(
因此,您可以放入swap
名称空间std并使其起作用。但是,您需要记住swap
在A
有模板的情况下放入的名称空间:A<T>
。而且因为如果你把这两种情况下将工作swap
中A
的命名空间,它只是更容易记住(并教其他人)只是做的一个方法。
template <>
输入您的第一个示例时,我得到exp::swap(A, A)
了gcc的输出。那么,为什么不喜欢专业化呢?
using std::swap
“从不在头文件中使用语句放置”是例外吗?其实为什么不放using std::swap
进去<algorithm>
呢?我想这可能会破坏少数人的代码。也许弃用支持并最终投入支持?
using std::swap
标头中的功能范围。是的,swap
几乎是一个关键字。但是不,它不是关键字。因此,除非确实需要,否则最好不要将其导出到所有名称空间。 swap
很像operator==
。最大的区别是,甚至没有人想到operator==
使用合格的名称空间语法进行调用(这太丑陋了)。
(按C ++标准)不允许您重载std :: swap,但是特别允许您将自己类型的模板特化添加到std名称空间。例如
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
那么在std容器(以及其他任何地方)中的用法将选择您的专业而不是一般的专业。
还要注意,提供交换的基类实现对于您的派生类型还不够好。例如,如果您有
class Base
{
// ... stuff ...
}
class Derived : public Base
{
// ... stuff ...
}
namespace std
{
template<>
void swap(Base& lha, Base& rhs)
{
// ...
}
}
这将适用于基类,但是如果您尝试交换两个派生对象,它将使用std的通用版本,因为模板交换是完全匹配的(并且避免了仅交换派生对象的“基本”部分的问题)。
注意:我已经对其进行了更新,以删除上一个答案中的错误位。天哪!(感谢puetzk和j_random_hacker指出来)
虽然通常不应该在std ::名称空间中添加内容是正确的,但明确允许为用户定义类型添加模板特化。没有重载功能。这是一个微妙的区别:-)
17.4.3.1/1对于C ++程序,除非另有说明,否则未声明将声明或定义添加到名称空间std或具有名称空间std的名称空间的定义。程序可以将任何标准库模板的模板专业化添加到名称空间std。除非声明依赖于用户定义的外部链接名称,并且除非模板专业化满足原始模板的标准库要求,否则标准库的这种专业化(全部或部分)会导致未定义的行为。
std :: swap的特殊化如下所示:
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
没有template <>位,它将是一个未定义的重载,而不是允许的特殊化。@Wilka的更改默认名称空间的建议方法可能与用户代码一起使用(由于Koenig查找更倾向于使用无名称空间的版本),但这并不能保证,实际上也并非如此(STL实现应完全使用合格的std :: swap)。
有一个++上comp.lang.c线,缓和了长期的话题标准探讨。不过,大多数内容都与部分专业化有关(目前尚无好方法)。
::swap
您添加过载更专业比std::swap
过载vector
,所以它抓住了电话,没有后者是相关的专业化。我不确定这是一个实际问题(但是我也没有声称这是一个好主意!)。