C++在资源管理类中提供对原始资源的访问

简介: C++在资源管理类中提供对原始资源的访问

1.问题的引入


在一个完美的世界里,资源管理类会帮你完成对资源的所有操作,自己不用关心资源管理类里面的原始资源。但现实是残酷的,有时候仍然需要自己直接接触资源管理类所封装的原始资源。


在之前的文章C++中基于对象来管理资源我们使用智能指针shared_ptr保存工厂函数createInvestment()的调用结果,如下所示:


1std::shared_ptr<Investment> pInv(createInvestment());


假设现在你希望用某个函数daysHeld()去处理Investment对象,如果你想像下面那样调用它,就会编译不通过。因为daysHeld()函数需要的参数是Investment*指针,但你传给它的参数却是shared_ptr<Investment>类型的对象。如下所示:


1int daysHeld(const Investment* pi);
2int days = daysHeld(pInv);


2.解决方法



为了解决上面的问题,你需要一个函数去将RAII类的对象(shared_ptr)转换为其内部所含有的原始资源(Investment*)有两个方法可以完成目标:显式转换和隐式转换。


方法1:显式转换


shared_ptr和auto_ptr都提供了一个get成员函数,用来执行显示转换。即它会返回智能指针内部的原始指针。如下所示:


1int days = daysHeld(pInv.get());


方法2:隐式转换


像所有的智能指针一样,shared_ptr和auto_ptr也重载了指针的解引用运算符(->和*),它们允许隐式转换底层原始指针。


1class Investment{
 2public:
 3    bool isTaxFree() const;
 4    // ...
 5
 6};
 7
 8Investment* createInvestment();  // 工厂函数用来返回指向Investment对象的指针
 9
10std::shared_ptr<Investment> pi1(createInvestment());    // 令shared_ptr管理一笔资源
11bool taxable1 = !(pi1->isTaxFree());   // 经由->去访问资源
12// ...
13
14std::auto_ptr<Investment> pi2(createInvestment());  // 令auto_ptr管理一笔资源
15bool taxable2 = !((*pi2).isTaxFree());  // 经*去访问资源


3.其他情形



有时候,我们需要把RAII资源管理类的对象所封装的原始资源拿出来,可以定义一个隐式转换函数,将资源管理类隐式或显式转换为原始资源。例如,要实现对C API中的字体类型(font)的资源管理。

1FontHandle getFont();   // C API定义的分配字体函数
2void releaseFont(FontHandle fh);  // C API定义的释放字体函数


下面,我们定义自己的RAII资源管理类Font,如下所示:


1class FontHandle{
 2    // ...
 3};
 4
 5FontHandle getFont();   // C API定义的分配字体函数
 6void releaseFont(FontHandle fh);  // C API定义的释放字体函数
 7
 8class Font{
 9public:
10    explicit Font(FontHandle fh): f(fh)   // C只能使用值传递
11    { 
12        // 构造时获取资源
13    }
14    ~Font(){
15        releaseFont(f);
16    }
17private:
18    FontHandle f;
19};


如果我们要使用某些C API,只能通过使用FontHandle类型。因此,就需要把Font类型显式转换为FontHandle类型,于是定义一个显式转换的函数get()。


1class Font{
 2public:
 3    explicit Font(FontHandle fh): f(fh)   // C只能使用值传递
 4    { 
 5        // 构造时获取资源
 6    }
 7    FontHandle get() const {return f;}  // 显示转换函数
 8    ~Font(){
 9        releaseFont(f);
10    }
11private:
12    FontHandle f;
13};


但是,上面这样的显式转换的一个缺点是每次使用都要调用get()函数,比较麻烦。如下所示:


1void changeFontSize(FontHandle f, int newSize);  // C API
2Font f(getFont());
3int newFontSize;
4// ...
5changeFontSize(f.get(), newFontSize);


另一个缺点是:既然我们写了RAII资源管理类,为什么还要每次只使用它的原始资源,这与我们希望避免资源泄漏的初衷背道而驰。因此,下面来看看隐式转换


1class Font{
 2public:
 3    explicit Font(FontHandle fh): f(fh)   // C只能使用值传递
 4    { 
 5        // 构造时获取资源
 6    }
 7    operator FontHandle() const {  // 隐式转换函数
 8        return f;
 9    }
10    ~Font(){
11        releaseFont(f);
12    }
13private:
14    FontHandle f;
15};


隐式转换会使得客户调用C API时会方便很多。如下所示:


1Font f(getFont());
2int newFontSize;
3// ...
4changeFontSize(f, newFontSize); // 隐式转换


但是,隐式转换也会增加发生错误的机会,某些类型错误就不会被编译器探测到。原本希望把一个Font对象拷贝进另一个Font对象,但如果某个人误把打Font打成了FontHandle,这样就把我们的资源管理对象变成了原始资源对象,编译器也不会报错。


1Font f1(getFont());
2
3// ...
4FontHandle f2 = f1;


结果:程序有一个FontHandle对象被f1封装和管理,但这个FontHandle通过上面的操作被拷贝进了f2,这样f1和f2同时控制着一个FontHandle。如果f1被释放,这个FontHandle也将被释放,就会导致f2的字体被损坏。


对于使用隐式转换还是显式转换要根据不同的需求来决定,如果想保证代码的正确性,显式转换的get()函数可能是更好的选择;如果想代码自然易懂,隐式转换可能更好,两者各有优缺点。


4.总结


(1) API通常需要使用原始资源作为参数,因此我们的RAII资源管理类要保证它所封装的资源是对外界可接触的。(2) 对原始资源的访问可以通过显式转换或隐式转换。一般来说,显式转换比较安全,但隐式转换对客户使用比较方便。

目录
打赏
0
0
0
0
6
分享
相关文章
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
39 16
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
19天前
|
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
64 6
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
2月前
|
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
92 19

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等