【问题标题】:Strange behavior of templated operator<<模板化操作符的奇怪行为<<
【发布时间】:2014-03-22 21:48:57
【问题描述】:

我无法理解操作员

标题:

#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_ */

矢量.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&lt;&lt; 声明。如果我删除 CONST 修饰符,编译器会说:Undefined reference to operator&lt;&lt;,但使用 const 它可以工作。有趣的是,在我的实现中,在 cpp 中,我没有 CONST。

顺便说一句,如何使用warning: friend declaration declares a non-template function 为操作员解决警告?

【问题讨论】:

  • 您的main() 是否在vector.cpp 或其他cpp 文件中?
  • @MichaelBurr 在 main.cpp 中。查看编辑。
  • 为了满足我的好奇心(顺便回答你的问题),翻阅代码非常重要。通过。
  • @wingsofovnia 似乎这个问题有你如何摆脱警告的答案:stackoverflow.com/questions/4147399/…

标签: c++ templates operator-overloading friend


【解决方案1】:

您应该学习如何将其归结为 Short, Self-Contained, Compilable Example aka Minimal Working 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&lt;&lt; 的函数,以便与这个已经存在的函数成为朋友。但它没有找到任何与这些参数类型匹配的东西。因此,它在周围(= 全局)命名空间中声明一个新函数。这个函数看起来像这样:

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

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

现在,您稍后声明一个同名的函数模板。但是当你在类模板before 中编写友元声明时,编译器无法知道你打算与这个函数模板交友。所以友元函数和函数模板这两者是无关的

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

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

对于Vector&lt;int&gt; 类型的这个参数,绑定到函数模板(特化)的Vector&lt;int&gt;&amp; 优于绑定到友元函数的Vector&lt;int&gt; const&amp;。因此,选择了函数模板,它有一个定义(函数体),一切都可以编译、链接和工作。请注意,函数模板未加好友,但这不会引发错误,因为您不使用任何私有成员。

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


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

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 声明了一个函数模板,随后在类的定义之后的命名空间范围内的声明重新声明这个函数模板。

【讨论】:

  • 谢谢你,@dyp。你更喜欢什么解决方案?最佳做法是什么?
  • 不确定这里的最佳做法是什么。通常,我会在类定义中实现类模板的短成员函数。但是如果你想保持类定义和成员函数定义严格分开,你可以使用第二种解决方案(带有前向声明)。我不会使用第三个,因为它适合所有专业。顺便提一句。包括.cpp 看起来很奇怪;一些库(例如 boost)使用 .ipp 来作为模板的“实现”部分(.hpp 用于普通头文件)。
猜你喜欢
  • 1970-01-01
  • 2011-08-06
  • 1970-01-01
  • 2017-07-27
  • 2014-01-31
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多