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

ovn*_*nia 0 c++ templates operator-overloading friend

我无法理解我班上的operator <<的行为:

标题:

#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_ */
Run Code Online (Sandbox Code Playgroud)

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() {}
Run Code Online (Sandbox Code Playgroud)

我用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;
}
Run Code Online (Sandbox Code Playgroud)

神奇的是operator<<在标题中的声明.如果我删除CONST修饰符,编译器说:Undefined reference to operator<<,但与const它的工作原理.有趣的是,在我的实现中,在cpp中,我没有CONST.

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

dyp*_*dyp 7

您应该学习如何将其简化为简短,自包含,可编译的示例,即最小工作示例.

这是一个证明问题的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;
}
Run Code Online (Sandbox Code Playgroud)

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


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

friend std::ostream& operator<<(std::ostream& o, Vector<T> const& v);
Run Code Online (Sandbox Code Playgroud)

这将查找operator<<周围范围中命名的函数,以与此已存在的函数成为一个伙伴.但它没有找到任何匹配那些参数类型.因此,它在around(= global)命名空间中声明了一个新函数.这个函数看起来像这样:

std::ostream& operator<<(std::ostream& o, Vector<T> const& v);
Run Code Online (Sandbox Code Playgroud)

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

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

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

Vector<int> v;
v.add(1);
v.add(2);
cout << v << endl; // v is not const
Run Code Online (Sandbox Code Playgroud)

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

一旦添加const到函数模板,函数模板就不再是参数的更好匹配.由于我们有一个具有相同重载"rank"的函数和函数模板,因此非模板是首选.Ergo,调用友元函数,它没有定义=>发生链接器错误.


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

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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


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

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;
}
Run Code Online (Sandbox Code Playgroud)

在这个解决方案中,friend-declaration声明了一个函数模板,并且在类''定义后的命名空间范围内的后一个声明重新声明了这个函数模板.