【问题标题】:Template Classes in C++ and Function OverloadingC++ 中的模板类和函数重载
【发布时间】:2018-05-25 14:18:36
【问题描述】:

我正在练习在 C++ 中使用模板和类。我的目标是为双端队列编写一个模板类。它将具有“insert_head”、“insert_tail”、“remove_tail”和“remove head”的功能,以及使用“cout”打印的能力。此外,“=”运算符必须能够用于将类的一个实例复制到另一个实例。这是我当前的代码:

#ifndef DEQUE_H
#define DEQUE_H

template <typename T>
class Deque {
public:
    Deque(int size = 0, int capacity = 1000) : size_(size), capacity_(capacity) 
    {}
    Deque(Deque & d) : x_(d.x()), size_(d.size()), capacity_(d.capacity()) {}


std::ostream & operator<<(std::ostream & cout) {
    cout << '[';

    if (size_ > 0) {
        for (int i = 0; i < (size_ - 1)* sizeof(T); i += sizeof(T)) {
            std::cout << *(x_ + i) << ',';
        }
        cout << *(x_ + (size_ - 1)* sizeof(T));
    }
    cout << ']';
    return cout;
}

Deque operator=(Deque d) {
    Deque dq(d);
    return dq;
}

void print_test() {
    std::cout << '[';

    if (size_ > 0) {
        for (int i = 0; i < (size_ - 1)* sizeof(T); i += sizeof(T)) {
            std::cout << *(x_ + i) << ',';
        }
        std::cout << *(x_ + (size_ - 1)* sizeof(T));
    }
    std::cout << ']';
}

int * x() {
    return x_;
}
int size() {
    return size_;
}
int capacity() {
    return capacity_;
}

bool is_empty() {
    return size_ == 0;
}

void insert_tail(T tail) {
    if (size_ < capacity_) {
        *(x_ + sizeof(T) * size_) = tail;
        size_++;
    } else {
        // throw overflow
    }
}
T remove_tail() {
    if (size_ > 0) {
        T ret = *(x_ + sizeof(T) * (size_ - 1));
        std::cout << ret;
        size_--;
        return ret;
    } else {
        // throw underflow
    }
}

void insert_head(T head) {
    if (size_ > 0 && size_ < capacity_) {
        for (int i = (size_ - 1) * sizeof(T); i < 0; i -= sizeof(T)) {
            *(x_ + i + sizeof(T)) = *(x_ + i);
        }
    }
    if (size_ < capacity_) {
        *x_ = head;
        size_++;
    } else {
        // throw overflow
    }
}

T remove_head() {

    if (size_ > 0) {
        T ret = *x_;
        for (int i = sizeof(T); i < size_* sizeof(T); i += sizeof(T)) {
            *(x_ + i - sizeof(T)) = *(x_ + i);
        }   
        size_--;
        return ret;
    } else {
        // throw underflow
    }
}

private:
    T * x_;
    int size_;
    int capacity_;
};

#endif

这是我使用该类的测试代码:

#include <iostream>
#include "Deque.h"

int main(int argc, char const *argv[])
{
    Deque< int > dq;


    dq.insert_head(1);

    // dq.insert_head(2); // adding head when not empty causes bug

    dq.insert_tail(3);
    dq.insert_tail(4);
    dq.insert_tail(5);
    dq.print_test(); std::cout << std::endl;

    // std::cout << dq; // '<<' not overloaded properly'

    std::cout << dq.remove_head() << " head removed\n";

    // int x = dq.remove_head(); // seg faults when assigning returned value to a variable 

    dq.insert_tail(2);
    dq.print_test();
    std::cout << std::endl;

    Deque< int > dq1(dq);
    Deque< int > dq2;

    // dq2 = dq1; // '=' not overloaded properly

    return 0;
}

我的四个问题中的每一个都在我的测试文件中被注释掉的代码行中,这里是进一步的解释:

  1. 当调用“dq.insert_head(2)”并且 dq 不为空(大小 > 0)时,我尝试将双端队列中的所有其他元素移到一个位置,以便可以在那里插入新值,有问题,元素没有移过来。

  2. "std::cout

  3. 当试图从双端队列中删除头部或尾部时,我试图返回删除的值。在未注释掉的代码行中,返回值按原样打印,但下面的代码行会导致 seg 错误。是因为我试图将模板变量分配给整数变量吗?

  4. 我的最后一个问题是 '=' 运算符没有将类的一个实例复制到另一个实例。我的目标是创建该类的一个新实例,然后返回该实例(如您在“Deque operator=(Deque d)”中所见),但这并没有像我希望的那样工作。使用模板类重载“=”函数的最佳方法是什么。

感谢您的帮助,非常感谢您回答这些问题。

【问题讨论】:

  • 你在哪里为x分配内存?删除您的 sizeof(T) 条款。此外,请考虑在适当的情况下使用 const 引用。
  • 1) 您可以通过使用调试器单步执行您的代码来找到 1、3、4 的原因(这就是任何想要回答您的人,很可能无论如何都会这样做)。 2) 关于 (2) - 您当前的重载接受 deque 作为第一个参数,ostream 作为第二个参数,有效地允许您将 `dq . If you want to output it via the use of cout ostream 作为第一个参数)。
  • 为什么要重载= 运算符?
  • 您的x 方法无条件返回int*,而不是T*。您的 operator= 没有为实例的任何成员分配东西,它只是创建一个新的 Deque 并返回它,这是没有意义的。实现复制构造和operator= 很难/很烦人;我强烈建议阅读并使用the copy-and-swap idiom 来显着减少重复代码;如果复制构建和销毁工作,operator= 很容易。
  • *(x+i) 语法最终可能会让您感到困惑。我建议使用数组语法x[i]

标签: c++ class templates cout overloading


【解决方案1】:

您的所有功能都有问题:

 Deque(int size = 0, int capacity = 1000) : size_(size), capacity_(capacity) {}
  • 如果允许指定大小,则必须为这些项目分配和初始化内存。您应该只指定容量。
  • x_ 未初始化。

假设你想要一个固定的容量,那么你的构造函数应该是:

Deque(int capacity = 1000) 
    : size_(0)
    , x_(new T[capacity])
    , capacity_(capacity) 
{
}

即使这是一个简化版本,因为它会为所有可能效率低下的项目调用构造函数,并要求T 具有可访问的默认构造函数。

现在是复制构造函数:

  • 拷贝构造函数应该做深拷贝。否则,您的程序将(可能)在删除您已复制的第一个 Deque 后崩溃,因为两次删除项目是未定义的行为。
  • 原型应采用常量引用,如:Deque(const Deque &amp;other);

代码如下所示:

Deque(const Deque &other)
    : capacity_(other.capacity_)
    , x_(new T[other.capacity_])
    , size_(other.size_)
{
    for (int i = 0; i != size_; ++i)
    {
        x_[i] = other.x_[i];
    }
}

对于&lt;&lt;,原型应该是:

friend std::ostream & operator<<(std::ostream &cout, const T &data)

假设它被声明在类中以访问私有字段。您需要传递操作员工作的数据。

对于赋值运算符,这样的事情可以工作:

Deque& operator=(const Deque &other)
{
    // Use swap idiom...
    Deque tmp(other);

    // Swap pointers so old x_ get destroyed...
    T *old_x = x_;
    x_ = tmp.x_;
    tmp.x_ = old_x;

    // Usually one would use std::swap. 
    // Here as tmp get destroyed, it is not strictly to swap capacity_ and size_.
    capacity_ = tmp.capacity_;
    size_ = tmp.size_;
}

现在对于 x() 函数: - 如果你做一个队列,你可能不想暴露数据,所以应该删除该函数。 - 如果保留,该函数应该是 const 并返回一个指向 T:T *x() const; 的指针以获得预期的功能。

sizecapacityis_empty 都应该是 const 成员函数。

insert_tailremove_tail 问题已在其他人 cmets 中得到解释(特别是无关的sizeof)。

insert_headremove_head 也存在类似问题。此外,复制现有项目的代码可以在私有函数中进行重构,以遵循 DRY 原则并避免代码重复。

【讨论】:

  • “friend std::ostream & operator
  • 您只需删除 friend 关键字。
【解决方案2】:

第一个问题的答案是删除sizeof(T),这样你就得到了这个

    for (int i = (size_ - 1); i > 0; i --) {
        *(x_ + i + 1) = *(x_ + i);
    }

第二个问题的答案是将 &lt;&lt; 重载的声明更改为 friend std::ostream &amp; operator&lt;&lt;(std::ostream &amp; x, Deque n) 并在类外初始化正文。

第三个问题的答案是,你不能返回一个 int 指针,它可能指向与 T 不同的内存块。

第四个问题的答案如下:

Deque& operator=(const Deque& d) {
    x_ = d.x_; // Deep copy
    size_ = d.size_;
    capacity_ = d.capacity_;
    return *this;
}

【讨论】:

  • 所以我在类中放了原型:friend std::ostream & operator
  • @Schnagl 没错!如果可行,请不要忘记投票并接受
  • 该解决方案解决了一些问题,但不是全部。您的= 运算符仍然不正确。首先,赋值运算符的原型应该几乎总是采用Deque &amp;operator=(const Deque &amp;other) 的形式。也就是说,您获取参考,并在更新后返回对此的参考。
  • @Phil1970 我修复了赋值运算符。
  • 不完全...您返回对堆栈上的变量的引用,该变量是未定义的行为,并且对象本身未修改。
猜你喜欢
  • 2016-10-03
  • 1970-01-01
  • 2013-06-11
  • 1970-01-01
  • 2010-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多