C++面试必备知识点-啊A爱爱

RAII

C++支持将对象存储在栈上,但是很多情况下不可以,比如:

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

常见的情况之一,在工厂方法或其他面向对象编程的情况下,返回值类型是基类。代码示例:

enum class shape_type{
    circle,
    triangle,
    rectangele,
};
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::cirle:
                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++中特有的一种编码错误,属于对象复制相关的语义错误。

那么应该如何做,才能确保create_shape的返回值不会发生内存泄漏呢?

答案就在析构函数和他的栈展开行为上。我们只需要将这个返回值放在一个本地变量里,并确保其析构函数会删除该对象即可。

简单的实现如下:

class shape_wrapper{
public:
    explicit shape_wrapper(shape* ptr=nullptr): ptr_(ptr){}
    ~shape_wrapper(){delete ptr_;}
    shape* get()const{ return ptr_;}
private:
    shapr* 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;
    }
}
if(ptr!=nullptr){
    ptr->~shape();
    operator_delete(ptr);
}

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

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

  • 关闭文件
  • 释放同步锁
  • 释放其他重要的系统资源
#面试##秋招#
全部评论
感谢学霸分享的c++知识点
点赞 回复 分享
发布于 2022-08-31 14:20 陕西

相关推荐

评论
7
11
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务