为C ++模板类提供swap()会破坏std :: swap()吗?

时间:2012-07-19 22:52:22

标签: c++ visual-studio-2010 visual-c++ argument-dependent-lookup copy-and-swap

我试图在我的自定义Matrix类中实现copy-and-swap idiom,并且在链接到问题中建议的方式中遇到了swap()的实现问题:

(我使用的编译器是MS VS2010 IDE中的一个,方言是老式的C ++ 03。)

// matrix.h

namespace my_space 
{

template<typename T> class Matrix
{
public:
    /* ... */

    friend void swap(Matrix<T> &first, Matrix<T> &second)
    {
        using std::swap;
        swap(first.width_, second.width_);
        swap(first.height_, second.height_);
        swap(first.data_, second.data_);
    }
};

} // namespace

现在我无法在驻留在此命名空间中的函数的代码中访问常规的std :: swap():

// some_code.cpp:
#include "matrix.h"
#include <algorithm>

using namespace my_space;
using namespace std;

// SomeClass is part of my_space!
void SomeClass::some_function()
{
    int a = 3, b = 7;
    swap(a,b);  // I wan't std::swap!
}

不幸的是,出于某种原因,Matrix的my_space::swap()似乎是对std::swap()的所有其他调用的别名,我不知道为什么因为参数不合适而且ADL应该支持{{1} }}:

std::swap

(对于我尝试使用的每一行1>f:\src\some_code.cpp(653): error C3767: 'swap': candidate function(s) not accessible 1> could be the friend function at 'f:\src\matrix.h(63)' : 'swap' [may be found via argument-dependent lookup] ,错误重复10次)

std::swap my_space::swap()是否始终否决std::swap() my_space,即使参数不合适?它不像std::swap()不可见,并且在my_space::swap()创建之前就可以正常工作。

4 个答案:

答案 0 :(得分:1)

Matrix中加入以下一行:

template<typename U> friend void swap(Matrix<U> &first, Matrix<U> &second);

并在课堂外定义swap。您收到错误function template has already been defined的原因是因为Matrix<unsigned short>Matrix<char>的每个实例都包含相同的交换函数定义,因为您在{{1}中定义了友元函数模板。

答案 1 :(得分:1)

以下使用VC ++ 2010 SP1为我完全构建:

Matrix.h:

#pragma once

#include <algorithm>
#include <vector>

namespace my_space
{
    template<typename T>
    class Matrix
    {
    public:
        Matrix(unsigned const w, unsigned const h)
          : width_(w), height_(h), data_(w * h)
        { }

    private:
        unsigned width_;
        unsigned height_;
        std::vector<T> data_;

        friend void swap(Matrix& lhs, Matrix& rhs)
        {
            using std::swap;
            swap(lhs.width_,  rhs.width_);
            swap(lhs.height_, rhs.height_);
            swap(lhs.data_,   rhs.data_);
        }
    };
}

的.cpp:

#include "Matrix.h"

int main()
{
    using namespace my_space;
    using std::swap;

    int a(0), b(1);
    swap(a, b);

    Matrix<int> c(2, 3), d(4, 5);
    swap(c, d);

    Matrix<short> e(6, 7), f(8, 9);
    swap(e, f);
}

由于您没有发布SSCCE提示提示),因此很难确切地看到您出错的地方,但是可以将此作为缩小问题范围的起点。

答案 2 :(得分:1)

STL容器采用的方法使用成员函数,然后重载静态函数。例如:

template<class T, class Alloc=std::allocator<T> >
class vector
{
   T *data;
   size_t n;
   size_t max_n;
public:
   void swap(vector<T, Alloc> &other)
   {
      swap(this->data, other.data);
      swap(this->n, other.n);
      swap(this->max_n, other.max_n);
   }
};

template<class T, class A>
void swap(vector<T, A> &lhs, vector<T, A> &rhs)
{
   lhs.swap(rhs);
}

在建议的Matrix类中,只需采用相同的方法......

namespace my_space
{
template<typename T>
class Matrix
{
   unsigned width_;
   unsigned height_;
   std::vector<T> data_;
public:
   void swap(Matrix<T> &other)
   {
      std::swap(this->width_, other.width_);
      std::swap(this->height_, other.height_);
      std::swap(this->data_, other.data_);  // calls this->data_.swap(other.data_);
   }
};
}

namespace std
{
   template<typename T>
   void swap(my_space::Matrix<T> &lhs, my_space::Matrix<T> &rhs)
   {
      lhs.swap(rhs);
   }
}

答案 3 :(得分:1)

如果代码与您发布的内容非常相似,则编译器存在问题。代码在clang ++中编译得很好,就像它应该的那样。

朋友声明很奇怪,因为它们声明了一个具有命名空间范围的函数,但声明只能通过ADL使用,即使这样,只有当至少有一个参数属于具有该朋友的类的类型时宣言。除非存在命名空间级别声明,否则该函数在命名空间范围内不可用。


测试1 (在命名空间级别没有明确声明的函数):

namespace A {
   struct B {
      friend void f();  // [1]
   };
   // void f();         // [2]
}
void A::f() {}          // [3]

在[1]中,我们添加了一个朋友声明,声明void A::f()A::B的朋友。如果在命名空间级别没有[2]中的附加声明,[3]中的定义将无法编译,因为在A命名空间之外,该定义也不是自我声明。


这里的含义是,因为该函数不能在命名空间级别进行查找,而只能通过Matrix<T>上的ADL(对于某些特定的实例化类型T),编译器无法找到作为两个int值交换的匹配。

在他的回答中,Jesse Good声明 Matrix和Matrix的每个实例都将包含相同的交换函数定义,因为您在Matrix模板中定义了友元函数,这是完全荒谬的。

在类中定义的友元函数将声明并定义命名空间级别函数,同样,声明只能在类中使用并可通过ADL访问。当在模板内完成此操作时,它将在命名空间级别为使用该函数的模板的每个实例化定义非模板化自由函数。也就是说,它将生成不同的定义。请注意,在类模板范围内,模板的名称标识正在实例化的特化,即在Matrix<T>内,标识符Matrix未命名模板,但是模板的一个实例化

测试2


namespace X {
   template <typename T>
   struct A {
      friend void function( A ) {}
   };
   template <typename T>
   void funcTion( A<T> ) {}
}
int main() {
   using namespace X;
   A<int> ai;    function(ai); funcTion(ai);
   A<double> ad; function(ad); funcTion(ad);
}

$ make test.cpp
$ nm test | grep func | c++filt
0000000100000e90 T void X::funcTion<double>(A<double>)
0000000100000e80 T void X::funcTion<int>(A<int>)
0000000100000e70 T X::function(A<double>)
0000000100000e60 T X::function(A<int>)

nm的输出是符号列表,c++filt会将受损的名称转换为C ++语法中的等价名称。程序的输出清楚地表明X::funcTion 已经为两种类型实例化的模板,而X::function是两个重载的非模板化函数。再次:两个非模板化函数。


声称它会生成相同的函数没什么意义,考虑到它有一个函数调用,比如说std::cout << lhs,代码必须选择operator<<的正确重载用于当前函数的实例化。没有单operator<<可以说是intunsigned longstd::vector<double>(没有什么能阻止您使用任何类型实例化模板

ildjarn的回答提出了另一种选择,但没有提供对行为的解释。替代方法有效,因为 using-directive using-declaration 完全不同。特别是,前者(using namespace X;)修改了查找,以便命名空间X中的标识符在命名空间级别中可用于当前代码段的一个封闭命名空间中(如果您构建了一个命名空间树,在包含X的分支符合包含使用 using-directive 的代码的分支的情况下,声明将可用。

另一方面, using-declaration using std::swap;)在上下文中提供函数std::swap声明。 em> using-declaration 存在。这就是您必须使用 using-declarations 而不是 using-directives 来实现交换功能的原因:

测试3


namespace Y { struct Z {}; void swap( Z&,Z& ); }
namespace X {
   struct A { int a; Y::Z b; };
   void swap( A& lhs, A& rhs ) {
      //using namespace std;       // [1]
      using std::swap;             // [2]
      swap( lhs.a, rhs.a );        // [3]
      swap( lhs.b, rhs.b );        // [4]
   }
}

如果我们使用了 using-directive [1],::std命名空间中的符号可用于在函数::X::swap(X::A&,X::A&)内执行的查找,就好像它们有已在::::std::X的共同祖先)中声明。现在,[3]中的交换将不会通过ADL找到任何swap函数,因此它将开始在封闭的命名空间中搜索swap函数。第一个封闭的命名空间是X,它确实包含swap函数,因此查找将停止并且重载决策将启动,但X::swap不是swap(int&,int&)的有效重载,所以它将无法编译。

通过使用 using-declaration ,我们将std::swap声明带到X::swap的范围内(函数内部!)。同样,ADL将不会应用于[3],并且将开始查找。在当前范围内(在函数内),它将找到std::swap的声明并将实例化模板。在[4]中,ADL确实启动了,它将在<{1}}函数内和/或swap中搜索<{1>}函数 中添加的::Y::Z函数,并添加那是在当前范围内找到的重载集(同样,::Y)。此时,::std::swap是一个比::Z::swap更好的匹配(给定完美匹配,非模板化函数比模板化函数更好匹配)并且您得到预期的行为。