模板化运算符的奇怪行为<<

时间:2014-03-22 21:48:57

标签: c++ templates operator-overloading friend

我无法理解运算符的行为<<在我班上:

头:

#ifndef VECTOR_H_
#define VECTOR_H_

#include <string>
#include <iostream>

template<class T>
class Vector {
        static const int EXPANDER = 10;
        T* array;
        int next;
        int length;
        void expand();
        void contract();
    public:
        Vector();
        Vector(const Vector& v);
        void add(const T e);
        T get(int index) const;
        bool removeByIndex(int index);
        bool remove(T e);
        int size() const;

        T operator[](int i) const;
        T& operator+=(const T& t);
        T operator+(const T& s);

        friend std::ostream& operator<< (std::ostream& os, const Vector<T>& obj);
        friend std::istream& operator>> (std::istream& is, Vector<T>& obj);

        std::string toString();
        ~Vector();
};

#endif /* VECTOR_H_ */

vector.cpp

#include "Vector.h"
#include <string>
#include <sstream>

template<class T>
Vector<T>::Vector() {
    length = EXPANDER;
    next = 0;
    array = new T[EXPANDER];
}

template<class T>
Vector<T>::Vector(const Vector& v) {
    length = v.next + 1 + EXPANDER;
    next = v.next;
    array = new T[length];
    for (int i = 0; i <= v.next; i++) {
        array[i] = v.array[i];
    }
}

template<class T>
void Vector<T>::add(const T e) {
    if (next >= length - 1)
        expand();
    array[next++] = e;
}

template<class T>
T Vector<T>::get(int index) const {
    if (index > next)
        return -1;
    return array[index - 1];
}

template<class T>
bool Vector<T>::removeByIndex(int index) {
    if (index > next)
        return false;
    for (int i = index; i < length; i++) {
        array[i] = array[i + 1];
    }
    next--;
    contract();
    return true;
}

template<class T>
bool Vector<T>::remove(T e) {
    int index = -1;
    for (int i = 0; i < next; i++) {
        if (array[i] == e) {
            index = i;
            break;
        }
    }
    if (index == -1)
        return false;
    return removeByIndex(index);
}

template<class T>
int Vector<T>::size() const {
    return next;
}

template<class T>
void Vector<T>::expand() {
    length += EXPANDER;
    T* temp = new T[length];
    for (int i = 0; i < next; i++) {
        temp[i] = array[i];
    }
    delete[] array;
    array = temp;
}

template<class T>
void Vector<T>::contract() {
    if (next + EXPANDER >= length)
        return; // NO need to contract

    length = next + EXPANDER + 1;
    T* temp = new T[length];
    for (int i = 0; i < next; i++) {
        temp[i] = array[i];
    }
    delete[] array;
    array = temp;
}

template<class T>
T Vector<T>::operator[](int i) const {
    return get(i);
}

template<class T>
T& Vector<T>::operator+=(const T& t) {
    for (int i = 0; i < t.size(); i++) {
        add(t.get(i));
    }
    return *this;
}

template<class T>
T Vector<T>::operator+(const T& s) {
    this += s;
    return this;
}

template<class T>
std::ostream& operator<< (std::ostream& os, Vector<T>& obj) {
    os << obj.toString();
    return os;
}

template<class T>
std::istream& operator>> (std::istream& is, Vector<T>& obj) {
    int size;
    T temp;
    is >> size;
    for (int i = 0; i < size; i++) {
        is >> temp;
        add(temp);
    }
    return is;
}

template<class T>
std::string Vector<T>::toString() {
    using namespace std;
    ostringstream sb;
    sb << "Elements(" << size() << "): [";
    for (int i = 0; i < next; i++) {
        sb << array[i] << ", ";
    }
    string r;
    r = sb.str();
    r = r.substr(0, r.size() - 2) + string("]");
    return r;
}

template<class T>
Vector<T>::~Vector() {}

我用main.cpp运行此代码

#include "Vector.h"
#include "Vector.cpp"
#include <string>
#include <iostream>
using namespace std;
int main() {
    Vector<int> v;
    v.add(1);
    v.add(2);
    cout << v << endl;
}

魔法在标题中的operator<<声明中。如果我删除CONST修饰符,编译器说:Undefined reference to operator<<,但使用const它可以工作。有趣的是,在我的实现中,在cpp中,我没有CONST。

顺便说一句,如何解决运营商使用warning: friend declaration declares a non-template function的警告?

1 个答案:

答案 0 :(得分:7)

你应该学会如何将其归结为Short, Self-Contained, Compilable Example又名最小工作范例。

这是一个证明问题的SSCCE:

#include <iostream>

template<class T>
class Vector
{
private:
    T m;

public:
    Vector(T p) : m(p) {}

    friend std::ostream& operator<<(std::ostream& o, Vector<T> const& v);

    T get() const { return m; }
};

template<class T>
std::ostream& operator<<(std::ostream& o, Vector<T>& v)
{
    // accessing a private member leads to a compiler error here:
    return o << "[function template]" << /*v.m*/ v.get();
}

// remove this function to get the same behaviour as in the OP
std::ostream& operator<<(std::ostream& o, Vector<int> const& v)
{
    return o << "function" << v.m;
}

int main()
{
    Vector<int> v(42);
    std::cout << v;
}

请注意,它只有大约30行,并且可以放在一个没有滚动条的屏幕上。


现在,问题是基于类模板中的朋友声明:

friend std::ostream& operator<<(std::ostream& o, Vector<T> const& v);

这会在周围的范围中查找名为operator<<的函数,以与此已存在的函数成为一个伙伴。但它没有找到任何匹配那些参数类型。因此,它在周围(=全局)命名空间中声明一个新函数。此功能如下所示:

std::ostream& operator<<(std::ostream& o, Vector<T> const& v);

(在全局命名空间中) 注意:它只能通过Argument-Dependent Lookup找到,如果它只通过friend-declaration声明。

现在,您稍后声明了一个同名的功能模板。但是,当您在之前在类模板中编写好友声明时,编译器无法知道您打算与此函数模板成为联系人。所以那两个,朋友功能和功能模板是不相关的

现在发生的是通常的重载分辨率。如果不添加const,则首选函数模板,因为您使用非const参数调用它:

Vector<int> v;
v.add(1);
v.add(2);
cout << v << endl; // v is not const

对于类型为Vector<int>的此参数,与函数模板(特化)的Vector<int>&的绑定优先于对友元函数的Vector<int> const&的绑定。因此,选择了函数模板,它具有定义(函数体),并且所有内容都编译,链接和工作。请注意,函数模板不是友好的,但由于您不使用任何私有成员,因此不会引发错误。

const添加到函数模板后,函数模板不再是参数的更好匹配。由于我们有一个具有相同重载“rank”的函数和函数模板,因此非模板是首选。问题是,调用了友元函数,它没有定义=&gt;发生链接器错误。


最简单的解决方案是在类定义中定义友元函数:

template<class T>
class Vector
{
private:
    T m;

public:
    Vector(T p) : m(p) {}

    friend std::ostream& operator<<(std::ostream& o, Vector<T> const& v)
    {
        return o << v.m;
    }
};

使用前向声明的解决方案:

template<class T>
class Vector;

template<class T>
std::ostream& operator<<(std::ostream& o, Vector<T> const& v);

template<class T>
class Vector
{
private:
    T m;

public:
    Vector(T p) : m(p) {}

    friend std::ostream& operator<< <T>(std::ostream& o, Vector<T> const& v);
};

template<class T>
std::ostream& operator<<(std::ostream& o, Vector<T> const& v)
{
    return o << v.m;
}

现在,编译器可以找到前向声明的函数模板并与现有函数(函数模板的特化)保持联系,而不是声明一个新函数。


与整个功能模板交朋友的解决方案:

template<class T>
class Vector
{
private:
    T m;

public:
    Vector(T p) : m(p) {}

    template<class U>
    friend std::ostream& operator<<(std::ostream& o, Vector<U> const& v);
};

template<class T>
std::ostream& operator<<(std::ostream& o, Vector<T> const& v)
{
    return o << v.m;
}

在这个解决方案中,friend-declaration声明了一个函数模板,并且在命名空间范围内的后面声明遵循类'definition redeclares 这个函数模板。