• 对象切片


    C++支持将对象储存在栈上,但很多情况,对象不能,或不应该存储在栈上。比如:

    • 对象很大
    • 对象的大小在编译时不能确定
    • 对象是函数的返回值,但由于特殊的原因,不应使用对象的值返回

    常见情况之一是,在工厂方法或其他面向对象编程的情况下,返回值类型是基类(的指针或引用)
    举例:是对工厂方法的简单演示:

    enum class shape_type {
      circle,
      triangle,
      rectangle,
      …
    };
    
    class shape { … };
    class circle : public shape { … };
    class triangle : public shape { … };
    class rectangle : public shape { … };
    
    shape* create_shape(shape_type type)
    {
      …
      switch (type) {
      case shape_type::circle:
        return new circle(…);
      case shape_type::triangle:
        return new triangle(…);
      case shape_type::rectangle:
        return new rectangle(…);
      …
      }
    }
    

    这个create_shape方法会返回一个shape对象,对象的实际类型是某个shape的子类,圆/三角形等。在这种情况下,函数的返回值只能是指针或其变体形式。如果返回类型是shape,实际却返回一个circle,编译器不会报错,但结果多半是错的,这种现象就叫做对象切片(object slicing),是C++特有的一种编码错误。这种错误不是语法错误,而是一个对象复制相关的语义错误,也算是C++的一个陷阱,所以要注意这种情况。

    那怎样才能确保在使用create_shape的返回值时不发生内存泄漏呢?
    答案就在析构函数和它的栈展开行为上。 只需要把这个返回值存放在一个本地变量里,并确保其析构函数会删除该对象即可。
    简单的实现如下:

    class shape_wrapper {
    public:
      explicit shape_wrapper(
        shape* ptr = nullptr)
        : ptr_(ptr) {}
      ~shape_wrapper()
      {
        delete ptr_;
      }
      shape* get() const { return ptr_; }
    private:
      shape* ptr_;
    };
    
    void foo()
    {
      …
      shape_wrapper ptr_wrapper(
        create_shape(…));
      …
    }
    

    delete空指针会发生什么?答案就是这是一个合法的空操作。

    在new一个对象和delete一个指针时编译器需要干不少的活,大致翻译如下:

    // new circle(…)
    {
      void* temp = operator new(sizeof(circle));
      try {
        circle* ptr =
          static_cast<circle*>(temp);
        ptr->circle(…);
        return ptr;
      }
      catch (...) {
        operator delete(ptr);
        throw;
      }
    }
    

    new的时候会先分配内存(失败时整个操作失败并向外抛出异常,通常是bad_alloc),然后在这个结果指针上构造对象;构造成功后则new操作整体完成,否则释放刚分配的内存并继续向外抛构造函数产生的异常。

    if (ptr != nullptr) {
      ptr->~shape();
      operator delete(ptr);
    }
    

    delete时先判断指针是否为空,在指针不为空时调用析构函数并释放之前分配的内存。

    回到 shape_wrapper 和它的析构行为。在析构函数里做必要的清理工作,这就是 RAII 的基本用法。这种清理并不限于释放内存,也可以是:

    • 关闭文件(fstream的析构就会这么做)
    • 释放同步锁
    • 释放其他重要的系统资源

    例如,我们应该使用:

    std::mutex mtx;
    
    void some_func()
    {
      std::lock_guard<std::mutex> guard(mtx);
      // 做需要同步的工作
    }
    

    而不是:

    std::mutex mtx;
    
    void some_func()
    {
      mtx.lock();
      // 做需要同步的工作……
      // 如果发生异常或提前返回,
      // 下面这句不会自动执行。
      mtx.unlock();
    }
    
  • 相关阅读:
    ubuntu16.04配置网卡
    如何让虚拟机的Ubuntu上网?
    sqlite错误 The database disk image is malformed database disk image is malformed 可解决
    Linux系统安装bcompare步骤及注意事项Linux系统安装bcompare步骤及注意事项
    用python做科学计算(一)C语言读取python生成的二进制文件
    ubuntu下的RapidSVN
    matplotlib常见问题总结
    MATLAB中的矩阵索引
    4.0 Lab1-CRC Generation(1)
    项目管理-人员配置
  • 原文地址:https://www.cnblogs.com/whiteBear/p/16727436.html
Copyright © 2020-2023  润新知