设为首页 加入收藏

TOP

[C++]资源管理(二)
2016-04-27 17:25:18 】 浏览:659
Tags:资源管理
12(m11); // 将m11复制到m12身上,会发生事? } }

当一个RAII对被复制,会发生什么事?以下是你的两种选择:
* 静止复制。许多时候允许RAII对象被复制并不合理。对一个像Lock这样的class却是可能的,因为很少能够合理拥有“同步化期初器物”的复件。如果复制对RAII class并不合理,就应该让该class继承uncopyable(见前文:)。
* 对底层资源使用“引用计数法”。有时候,我们希望保有资源,直到它的最后一个使用者被销毁,这种情况下复制RAII对象,该资源的引用计数递增。

问题解决

通常只要内含shared_ptr成员变量,就可以实现reference-counting copying行为。并且shared_ptr允许指定所谓的“删除器”(deleter)(一个函数或函数对象),当引用次数为0时便被调用,而不是执行析构函数。删除器对shared_ptr构造函数来说是可有可无的第二参数。

class Lock {
public:
    explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) {
        lock(mutexPtr.get()); // 随后会讨论get
    }
private:
    shared_ptr
   
     mutexPtr; }
   

此处不再需要声明析构函数,因为没有必要。因为class析构函数会自动调用其non-static成员变量的析构函数,也就是此处的删除器函数。

资源复制的原则

复制底部资源。有时候,只要你喜欢,可以针对一份资源拥有其任意数量的复制(副本)。而你需要“资源管理类”的唯一理由是,当你不再需要某个复件时确保它被释放。在此情况下复制资源管理对象,应该同时也复制其所包括的资源,也就是进行“深拷贝”。 转移底部资源的拥有权。某些罕见场合下,你可以希望确保永远只有一个RAII对象指向一个未加工资源,即使是RAII对象被复制依然如此,此时,资源的拥有权会从被复制物转移到目标物。就像auto_ptr的复制意义。

3. 在资源管理类中提供对原始资源的访问

API往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的方法。

问题产生

在#1中我们提到了以下factory函数

investment* pInv = createinvestment();

假设我们希望以某个函数处理investment对象:

int daysHeld(const investment* pi); //  返回投资天数

int days = daysHeld(pInv);  
// error!!! 类型不匹配!函数需要investment指针,而你传给它类型为shared_ptr的对象。

问题解决

这时候我们需要一个函数可将RAII class对象转换为其所内含之原始资源。以下提供两种方法:

显示转换

shared_ptr和auto_ptr都提供了一个get函数用来执行显示转换。

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

同样的,就像几乎所有智能指针一样,shared_ptr和auto_ptr都重载了指针取值的操作符operator -> and operator*。

隐式转换

提供一种重载操作符,完成隐式转换。类似于以下代码:

class type {
public:
    type(int i) : a(i) {
    }
    operator int() {
        return a;
    }
private:
    int a;
};
int main(int argc, const char * argv[]) {
    type a(10);
    std::cout << a << std::endl;
    return 0;
}

总结

是否应该提供一个显式转换函数将RAII class转换为其底部资源,或者应该提供隐式转换,取决于RAII class被设计执行的特定工作,以及它被使用的情况。最佳的设计很可能是坚持“让接口容易被正确使用,不易被误用”。通常显式转换函数如get是比较好的,因为它将“非故意之类型转换”的可能性最小化了。

4. 成对使用new和delete时要采取相同形式

当你使用new时,有两件事情发生:1)内存被分配出来。2)针对此内存会有一个或多个构造函数被调用。同样的,当你使用delete时,也有两件事情发生:1)针对此内存会有一个或多个析构函数被调用。2)内存被释放。但问题出来了,即将被删除的内存之内究竟有多少个对象?这决定了有多少个析构函数被调用。

因为单一对象的内存分配和数组的内存分配是不一样的,所以每当我们使用delete的时候要告诉编译器,我们使用的是单一对象还是数组。方法就是加上[]!

此规则对于喜欢使用typedef的人十分重要,这意味着作者必须说清楚typedef是什么,要用什么类型的delete。

typedef std::string AddressLines[4];

std::string* pal = new AddressLines;

delete [] pal; // that is right! But user may misunderstand.

为了避免诸如此类的错误,最好不要对数组形式采用typedef动作,而是使用vector,string等template来替换数组。

5. 以独立语句将new对象直接放入智能指针

我们无法知道编译器会按照什么样的顺序来执行一条代码语句,而这其中可能隐含着某些异常。

问题产生

假设我们有个函数来揭示处理程序的优先权,另一个函数用来在某动态分配所得的Widget上进行某些带有优先权的处理。

// function definition:
int priority();
void processWidget(std::shared_prt
   
     pw, int priority); // function reference: processWidget(std::shared_ptr
    
   

问题发生得很隐蔽!

我们本以为把一个对象放入”对象资源管理器“就可以有效的解决资源泄露的问题,但其实在这里隐含危机。问题出在编译器运行顺序上,如果编译器按照以下顺序:
1. 执行new Widget
2. 调用priority
3. 调用shared_ptr构造函数
而此时,priority的调用导致异常,那么new Widget返回的指针就会遗失,导致内存泄露。(幸福来得太突然)

问题解决

此问题的解决非常的简单。既然编译器有机会因为代码运行顺序不同而出错,那么我们就强行让他按照我们预想的顺序执行就可以了~

std::shared_ptr
    
      pw(new Widget); processWidget(pw, priority());
    

因为编译器对于“跨越语句的各项操作”没有重新排列的自由!所以此代码不会出现问题!

所以,尽可能单独地把new对象直接放入智能指针中总是有道理的。

6. 区别堆和栈

管理方式不同
栈,由编译器自动管理,无需程序员手工控制;堆:产生和释放由程序员控制。 空间大小不同
栈的空间有限;堆内存可以达到4G。 能否产生碎片不同
栈不会产生碎片,因为栈是种先进后出的队列。堆则容易产生碎片,多次的new/delete
会造成内存的不连续,从而造成大量的碎片。 生长方向不同
堆的生长方式是向上的,栈是向
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇c++内存管理 下一篇再说c++虚析构函数

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目