0

我试图在我的自定义 Matrix 类中实现复制和交换习语,但我在以链接到问题中建议的方式实现 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!
}

不幸的是,由于某种原因,my_space::swap()对于 Matrix 似乎将所有其他调用别名为std::swap(),我不知道为什么因为参数不适合并且 ADL 应该支持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()in my_space,即使论点不合适?它不是std::swap()不可见的,它在my_space::swap()创建之前工作正常。

4

4 回答 4

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命名空间之外该定义也不是自我声明。


这里的含义是,因为该函数不可用于在命名空间级别查找,而只能通过 ADL on Matrix<T>(对于某些特定的实例化类型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<<可以说 aint和 an unsigned longor std::vector<double>(没有什么能阻止你用任何类型实例化模板。

ildjarn 的答案提出了一个替代方案,但没有提供对该行为的解释。替代方法有效,因为using-directive与using-declaration完全不同。特别是,前者using namespace X;(包含满足包含使用 using 指令的代码的分支)。X

另一方面,using-declaration ( using std::swap;)在存在using-declarationstd::swap上下文中提供函数的声明。这就是为什么您必须使用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 指令​​[1],则命名空间中的符号::std将可用于在函数内部执行的查找,::X::swap(X::A&,X::A&)就好像它们已在中声明::(这是::stdand的共同祖先::X)。现在 [3] 中的交换将不会swap通过 ADL 找到任何函数,因此它将开始在swap封闭的命名空间中搜索函数。第一个封闭的命名空间是X,它确实包含一个swap函数,因此查找将停止并且重载解析将启动,但X::swap它不是有效的重载swap(int&,int&),因此它将无法编译。

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


于 2012-07-20T04:03:01.480 回答
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>Matrix

于 2012-07-19T23:33:36.890 回答
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);
   }
}
于 2012-07-20T03:37:24.397 回答
1

使用 VC++ 2010 SP1 为我构建了以下内容:

矩阵.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提示提示),因此很难准确看出哪里出错了,但您可以以此为起点来缩小问题范围。

于 2012-07-20T00:10:04.820 回答