c++赋值语句区别,构造函数与操作符重载

主要内容

最近偶遇一段代码两个奇妙的bug,简单记录一下,

一、等号的差别

即看似相同的赋值语句 = 号却可能有着完全不同的涵义。

class T {…};
T a = b;

T a;
a = b;

之间有着重要的差别。前者会调用构造函数 T(const T &b);,后者会调用重载操作符T &operator=(T &b) const;,另外作为参数传递的时候也有可能会触发构造函数。

void foo(T x); // 触发构造函数
void goo(T &x); // 不触发构造函数

因此,在重载赋值操作时,务必要记得等价地重载构造函数!!!!

二、delete 与浅拷贝

在对指针 delete 时可能会触发意外的 bug。

class T { P * ptr; };
T a = b;
delete a.ptr;

在触发浅拷贝时,指针指向也会一并被拷贝,因此释放 a.ptr 等同于释放 b.ptr

代码展示

以下为遇见的bug具体代码,以说明问题可能有多么严重。

class IntervalTree; // 某树算法

template <class T> class MyTree { // 对 IntervalTree 封装
private:
  bool _isIndexed;
  IntervalTree<uint32_t, T *> *_tree;
  std::set<T *> data;

  void buildIndex() { // 根据 data 构建树
    if (_isIndexed)
      return;
    if (_tree != nullptr)
      delete _tree;
    tree = new IntervalTree<uint32_t, T *>(...);    
    _isIndexed = true;
  }
public:
  MyTree() : _isIndexed(false), _tree(nullptr) {}
  ~MyTree () { delete _tree; }
  MyTree<T> &operator=(const MyTree<T> &other) {
    this->_isIndexed = false;
    this->_tree = nullptr;
    this->data = other.data;
    return *this;
  }
  void insert(T *var) {
    data.insert(var);
    _isIndexed = false;
  }
  bool find(uint32_t s, uint32_t e) {
    if (!_isIndexed)
      buildIndex();
    if (_tree == nullptr)
      return false;
    return !_tree->findOverlapping(s, e).empty();
  }
}

上述代码对外部类 IntervalTree 进行了简单的封装管理,以保证 data 的更新与树更新的同步关系,看上去就非常安全。。。。。

然而当我做如下操作的时候,bug悄然出现:

class Range;
void foo1(MyTree<Range> &x) {
  MyTree<Range> y = x; // 触发默认构造函数
  ...
}
void foo2(MyTree<Range> y) { // 触发默认构造函数
  ...
}
int main() {
  MyTree<Range> x;
  MyTree<Range> z;
  ...
  foo1(x); // BUG 触发 (foo2同理)
  ...
  return 0;
}

这里触发了严重的 BUG,并且不会报错

foo1 中看似是赋值语句的写法,实际调用的却是默认构造函数,浅拷贝每个域,从而导致 y._treex._tree 指向相同。另外换做 foo2 写法,作为函数参数传递时,同样调用一次默认构造函数,同样导致 y._treex._tree 指向相同。

当退出函数时,会触发 y 的析构函数释放 y._tree,导致 x._tree 被释放。同样,当重建 y 树时也会释放 y._tree,导致 x._tree 被释放。

在某种机缘巧合下,过了很久该代码都没有出现明显的错误!!!!

BUG 发现

很长一段时间内该代码都没有表现出任何异常,并持续运行着,直到某一天我发现对另一个不相干的树 z 的查询结果检查与预期不符。。。。。

原来,x._tree 被释放后,意外指向了另一个树 z._tree 的某子树上,导致程序 “貌似正确地” 被执行了下去。

在接下来的某一步,代码对 x._tree 进行修改并重建,间接导致了 z._tree 的子树被修改。从而改变了 z._tree 的查询结果。 (不知为何这里也没有任何报错信息???)

再接着某一天,我对 z._tree 上的另一个算法进行深入追踪时,意外注意到某些异常值。。。。

小结

1、delete 某个域时需要慎之又慎,需要注意到浅拷贝时,代码的正确性,最好每个类都写上构造函数 T(const T &b);
2、T &operator=(T &b) const;T(const T &b); 会让 = 存在差异性,因此二者代码逻辑最好完全一致,以避免出现混淆。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值