【问题标题】:C++: Defined my own assignment operator for my type, now .sort() wont work on vectors of my type?C++:为我的类型定义了我自己的赋值运算符,现在 .sort() 不适用于我的类型的向量?
【发布时间】:2011-01-26 16:11:20
【问题描述】:

我有一个类(读过Accelerated C++的人可能会觉得这个类很熟悉)定义如下:

class Student_info{
public:
    Student_info() : midterm(0.0), final(0.0) {};
    Student_info(std::istream& is){read(is);};

    Student_info(const Student_info& s);

    ~Student_info();

    Student_info& operator=(const Student_info& s);

    //Getters, setters, and other member functions ommited for brevity

    static int assignCount;
    static int copyCount;
    static int destroyCount;

private:
    std::string name;
    double midterm;
    double final;
    double finalGrade;
    std::vector<double> homework;

};

typedef std::vector<Student_info> stuContainer;


bool compare(const Student_info& x, const Student_info& y);

函数calculator() 使用这种类型的对象。作为函数的一部分,使用库的通用排序函数对(已声明的)Student_info 对象的向量进行排序。我的程序没有超过这一点(尽管根据 NetBeans 没有抛出异常并且程序正确退出)。

排序函数大量使用容器中保存的任何类型的赋值运算符,但我似乎无法找出我定义的那个有什么问题(程序在我定义它之前运行正常)。根据 Accelerated C++(或者至少我是这样解释的),赋值运算符应该工作的正确方式是首先破坏左操作数,然后用等于右操作数的值再次构造它。所以这是我重载的 operator= 定义:

Student_info& Student_info::operator=(const Student_info& s)
{
    if(this != &s)
    {
        this->~Student_info();
        destroyCount++;

        *this = s;
    }

    return *this;
}

如您所见,它调用了 Student_info 复制构造函数,定义如下:

Student_info::Student_info(const Student_info& s)
{
    name = s.name;
    midterm = s.midterm;
    final = s.final;
    finalGrade = s.finalGrade;
    homework = s.homework;

    copyCount++;
}

复制构造函数正确运行,因为省略排序语句允许程序正确运行并产生大于 0 的 copyCount(仅在复制构造函数和 operator= 中修改)。

那么我的赋值运算符到底出了什么问题?它与调用 Student_info 对象的破坏有关,但我不知道如何纠正它,除非不破坏它。

(顺便说一句,Accelerated C++ 中的一个练习要求创建复制构造函数、析构函数和赋值运算符……我意识到这些函数的合成版本显然足以满足我的课程)

【问题讨论】:

  • "根据 Accelerated C++ ..." -- 章节和诗句好吗?我想看看它到底说了什么误导了你。
  • 当然。它在第 11 章,特别是 11.3.3 “赋值不是初始化”。它是该小节中的第二段,内容如下:“关键区别源于两个观察结果:赋值 (operator=) 总是消除先前的值;初始化永远不会这样做。”然后继续给出使用“=”导致初始化的代码示例,以及导致赋值的代码示例。所以换句话说,使用“=”的上下文决定了是初始化还是赋值。
  • @Kevin:这种差异是语法上的。这是int a = 10;int a; a = 10; 之间的区别,与如何实现operator= 无关——也就是说,int a = 10; 将成为复制构造函数调用,而不是赋值运算符调用。在重载operator= 时,您不需要知道或理解这种差异。至于p197上的代码样例,因为我没有副本,所以我无法定义它是对还是错,虽然如果它提倡destruct-and-copy-construct模式,那肯定是不正确的。
  • 您会注意到使用了 obliterate 这个词而不是 destroy。虽然我没有亲自问过他们,但我敢打赌安德鲁和芭芭拉故意避免使用破坏这个词,因为它太接近于破坏者这个词。他们只是说有一个先前的值,而您正在删除该值并用另一个值替换它。这与初始化形成对比,初始化不会替换预先存在的值。通常,这种区别是微不足道的。例如删除一个 int 的先前值并用一个新值替换它需要完全相同的操作。
  • 我认为这个类根本不需要用户定义的拷贝构造函数、拷贝赋值运算符和析构函数,类不管理资源,自动生成的特殊成员函数完全美好的。您还希望第二个构造函数是显式的。

标签: c++ sorting copy-constructor assignment-operator


【解决方案1】:

不,不,不。它根本不应该那样工作。您当前的赋值运算符破坏了它被调用的对象,然后在被破坏的对象上调用自身(哦,嘿,无限递归)(哦,嘿,未定义的行为)。你不应该破坏现有的对象。完全没有。这段代码*this = s 根本不调用任何构造函数,它调用赋值运算符——这就是你刚刚定义的。复制构造函数调用看起来像new (this) Student_info(s);。这是一种已知的模式,在很多方面都很糟糕。如果你有推荐的书,把它扔进垃圾桶

赋值运算符应该将数据从右侧复制到左侧。在大多数情况下,最简单的方法就是复制每个数据成员。这个操作符的语义不涉及破坏任何东西。使用此运算符的任何人都有权期望不会破坏任何 Student_info 对象。

只需调用成员现有的赋值运算符,然后实现您需要的任何附加逻辑。

【讨论】:

  • 问题要求用户修改类,以便能够计算该类的对象被复制、分配或销毁的次数。这是我看到这一切完成的唯一方法。实现赋值运算符以使其模仿合成版本的正确方法是什么?我可以做到这一点,只需在函数末尾添加额外的逻辑。
  • @Kevin -- 要模拟默认赋值运算符,只需将赋值运算符应用于每个成员。例如name = s.name; midterm = s.midterm; 等等...
  • @Pigben:啊……谢谢。你能澄清一下我作为对原始帖子的评论发布的 Accelerated C++ 引用是什么意思吗?我很困惑。
  • @Kevin:简而言之:“SomeType var = value;”并且默认参数值是初始化,= 的所有其他用途都是赋值。
  • @Kevin:我发表了一条应该更简单地解释它的评论。
【解决方案2】:
*this = s;

这是无限递归。它不是复制构造函数,它是赋值运算符

【讨论】:

    【解决方案3】:
    struct Student_info {
      Student_info& operator=(Student_info other) {
        swap(*this, other);
        return *this;
      }
    
      friend void swap(Student_info &a, Student_info &b) {
        using std::swap;
        #define S(N) swap(a.N, b.N);
        S(name)
        S(midterm)
        S(final)
        S(finalGrade)
        S(homework)
        #undef S
      }
    
    private:
      std::string name;
      double midterm;
      double final;
      double finalGrade;
      std::vector<double> homework;
    };
    

    为简洁起见,省略了 Getter、setter 和其他成员函数

    如果您有只是样板的公共 getter 和 setter,请考虑将相应的数据成员标记为公共。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-22
      • 1970-01-01
      • 2018-06-02
      • 2014-10-07
      • 1970-01-01
      • 1970-01-01
      • 2014-05-24
      相关资源
      最近更新 更多