您的位置:首页 > 其它

使用Boost进行资源管理

2017-03-30 10:19 190 查看
以下代码来源于《深入实践Boost:Boost程序库开发的94个秘笈》一书

管理作用域内的类指针

在某些情况下,需要动态分配内存,并在分配的内存中构造一个类,比如:

void foo1()
{
foo_class* p = new foo_class("Some initialization data");
bool something_else_happened = some_function1(p);
if(something_else_happened)
{
delete p;
return false;
}
some_function2(p);
delete p ;
return true;
}


以上代码看似正确,但若some_function1()或some_function2()抛出一个异常,p就不会被删除,当然,也可以通过增加try{}catch{}块来处理,但是代码会难看且难读。

Boost.SmartPtr库中提供boost::scoped_ptr类,有更好的解法:

#include <boost/scoped_ptr.hpp>

bool foo3()
{
boost::scoped_ptr<foo_class> p(new foo_class(
"Some initialization data"));
bool something_else_happened = some_function1(p.get());
if (something_else_happened)
{
return false;
}
some_function2(p.get());
return true;
}


boost::scoped_ptr的工作原理:

在析构函数钟,Boost::scoped_ptr<T>将对它所存储的指针调用delete。当抛出一个异常时,堆栈是展开的,并且调用scoped_ptr的析构函数。

scoped_ptr<T> 的类模板是不可复制的,它只存储一个类指针,并且不需要T是一个完整的类型(也就是说可以前向声明)。一些编译器再删除一个不完整的类型时并不发出警告,这可能会导致难以检测的错误,但scoped_ptr(以及所有在Boost.SmartPtr中的类)在这些情况下有一个特定的编译时断言。

boost::scoped_ptr<T> 函数等于const std::auto_ptr<T> ,但它也有reset()函数。

跨方法使用的类指针的引用计数

在不同的执行线程中处理一个包含数据的动态分配的结构,很难去处理释放的问题,在Boost(和C++11)中有一个可以处理这种多线程问题的类,boost::shared_ptr。

#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
// 使用thread库,需要链接libboost_thread以及libboost_system库

void process_sp1(const boost::shared_ptr<foo_class>& p);
void process_sp2(const boost::shared_ptr<foo_class>& p);
void process_sp3(const boost::shared_ptr<foo_class>& p);

void foo()
{
typedef boost::shared_ptr<foo_class> ptr_t;
ptr_t p;
while (p = ptr_t(get_data()))
{
boost::thread(boost::bind(&process_sp1, p)).detach();
boost::thread(boost::bind(&process_sp2, p)).detach();
boost::thread(boost::bind(&process_sp3, p)).detach();
}
}


另一个例子

#include <string>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>

void process_str1(boost::shared_ptr<std::string> p);
void process_str2(const boost::shared_ptr<std::string>& p);

void foo()
{
boost::shared_ptr<std::string> ps =
boost::make_shared<std::string>(
"Guess why make_shared<std::string> "
"is faster than shared_ptr<std::string> "
"ps(new std::string('this string'))"
);
// make_shared()函数要比直接创建shared_ptr对象的方式快且高效,
// 因为它内部仅分配一次内存,消除了shared_ptr 构造时的开销。
boost::thread(boost::bind(&process_str1, ps)).detach();
boost::thread(boost::bind(&process_str2, ps)).detach();
}


boost::shared_ptr的工作原理:

shared_ptr类的内部有一个原子引用计数器。当复制它时,引用计数器递增,而当调用它的析构函数时,引用计数器递减。当引用计数器等于0时,对由share_ptr指向的对象调用delete。

管理作用域内的数组指针

前面解决了如何管理资源指针,但是,当处理数组时,需要调用delete[]。而Boost.SmartPointer库不仅有scoped_ptr<> 类,而且有scoped_array<> 类。

#include <boost/scoped_array.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>

void may_throw1(const char* buffer);
void may_throw2(const char* buffer);

void foo()
{
boost::scoped_array<char> buffer(new char[1024 * 1024 * 10]);

may_throw1(buffer.get());
may_throw2(buffer.get());

// 不需要调用delete[]
// buffer的析构函数会调用delete[]
}


跨方法使用的数组指针的引用计数

#include <cstring>
#include <boost/thread.hpp>
#include <boost/bind.hpp>

void do_process(const char* data, std::size_t size);

void do_process_in_background(const char* data, std::size_t size)
{
// 我们需要复制数据,因为我们不知道它什么时候会被调用者释放
char* data_cpy = new char[size];
std::memcpy(data_cpy, data, size);

// 启动执行线程来处理数据
boost::thread(boost::bind(&do_process, data_cpy, size)).detach();

// 不能delete[] data_cpy,因为do_process可能仍然再使用
}


以上代码问题,使用Boost.SmartPtr库,有三种解决方案。它们的区别是data_cpy变量的类型和构造。

// Method 1
#include <cstring>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_array.hpp>

void do_process(const char* data, std::size_t size);
void do_process1(const boost::shared_array<char>& data, std::size_t size)
{
do_process(data.get(), size);
}

void do_process_in_background_v1(const char* data, std::size_t size)
{
// 我们需要复制数据,因为我们不知道它什么时候会被调用者释放
boost::shared_array<char> data_cpy(new char[size]);
std::memcpy(data_cpy.get(), data, size);

// 启动执行线程来处理数据
boost::thread(boost::bind(&do_process1, data_cpy, size)).detach();

// 不需要对data_cpy调用delete[],
// 当引用计数为零时,data_cpy的析构函数将释放数据
}


// Method 2
#include <cstring>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

void do_process(const char* data, std::size_t size);
void do_process_shared_ptr(const boost::shared_ptr<char[]>& data, std::size_t size)
{
do_process(data.get(), size);
}

void do_process_in_background_v2(const char* data, std::size_t size)
{
// 执行速度比第一个方法快
boost::shared_ptr<char[]> data_cpy = boost::make_shared<char[]>(size);
std::memcpy(data_cpy.get(), data, size);

// 启动执行线程来处理数据
boost::thread(boost::bind(&do_process_shared_ptr, data_cpy, size)).detach();

// 当引用计数为零时,data_cpy的析构函数将释放数据
}


// Method 3
#include <cstring>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

void do_process(const char* data, std::size_t size);
void do_process_shared_ptr2(const boost::shared_ptr<char>& data, std::size_t size)
{
do_process(data.get(), size);
}

void do_process_in_background_v3(const char* data, std::size_t size)
{
// 执行速度比第一个方法快
boost::shared_ptr<char> data_cpy(new char[size],
boost::checked_array_deleter<char>());
std::memcpy(data_cpy.get(), data, size);

// 启动执行线程来处理数据
boost::thread(boost::bind(&do_process_shared_ptr2, data_cpy, size)).detach();

// 当引用计数为零时,data_cpy的析构函数将释放数据
}


工作原理:

这些例子,共享类对引用计数并且当引用计数为零时对一个指针调用delete[]。第三个例子中,为共享指针提供了一个deleter对象,将调用deleter对象来取代对delete的默认调用。

对比:

第一个方法是Boost的传统方法,第二个方法在Boost1.53之前没有实现,但是第二个方法是最快的一个(调用的new较少),第三个方法是可移植性最好的,可以用于旧版本的Boost与C++11 STL的shared_ptr<> (只是要将boost::checked_array_deleter<T> ()改为std::default_delete<T[]> ())。

在变量中存储任意函数化对象

观察以下代码,我们要传递函数化对象

#include  <functional>  // std::unary_function<>模板所需要

// 为接受int并且不返回任何东西的函数指针制作一个typedef
typedef void(*func_t)(int);
// 它不能接受函数化对象
void process_integers(func_t f);

class int_processor :public std::unary_function<int, void>
{
const int min;
const int max;
bool& triggered;

public:
int_processor(int min,int max,bool& triggered)
:min(min),max(max),triggered(triggered)
{}

void operator()(int i) const
{
if (i<min || i>max)
{
triggered = true;
}
}
};


要修复以上代码,使得process_integers接受函数化对象:

解决方案:使用Boost.Function库,它可以让你存储任何函数、成员函数或函数化对象,如果它的签名与模板参数中所描述的匹配:

#include <boost/function.hpp>

typedef boost::function<void(int)> fobject_t;
// 现在可以接受函数化对象
void process_integers(const fobject_t& f);

int main()
{
bool is_triggered = false;
int_processor fo(0, 200, is_triggered);
process_integers(fo);
std::cout << is_triggered << std::endl;
return 0;
}


boost::function类都有一个默认的构造函数和一个空的状态。

检查一个空的/默认构造状态可以这样做:

void foo(const fobject_t& f)
{
// boost::function 是可以转换成bool的
if (f)
{
// 'f'中有值
// ...
}
else
{
// 'f'为空
// ...
}
}


fobject_t方法存储在它本身来自函数化对象的数据中,并删除它们的确切类型。使用boost::function对象是安全的,如下面的代码:

bool g_is_triggered = false;
void set_functional_object(fobject_t& f)
{
int_processor fo(100,200,g_is_triggered);
f = fo;
// 'fo'离开作用域并且将被销毁,但'f'即使在作用域外也是可用的
}


尽管Boost.Function库经过多方面的优化,可以存储小的函数化对象,而无须额外的内存分配并且拥有优化的移动赋值运算符,被接受为C++11 STL库的一部分,在头文件<functional> 的std::命名空间中定义,但是,boost::function意味着编译器的优化障碍,这意味着:

std::for_each(v.begin(),v.edn(),
boost::bind(std::plus<int>(),10,_1));


将比下面的代码更好地被编译器优化

fobject_t f(boost::bind(std::plus<int>(),10,_1));
std::for_each(v.begin(),v,end(),f);


所以,当不是真的需要它时,应该尽量避免使用Boost.Function,在某些情况下,C++11 auto关键字可以方便地代替它:

auto f = boost::bind(std::plus<int>(),10,_1);
std::for_each(v.begin(),v.end(),f);


在变量中传递函数指针

继续前面的例子,现要在process_integers()方法中将一个指针传递给一个函数。

void my_ints_function(int i);
int main()
{
// 没什么需要做的,因为boost::function<>也可以从函数指针构造
process_integers(&my_ints_function);
}


一个指向my_ints_function的指针将存放在boost::function类里面,并且对boost::function的调用将被转发到所存储的指针。

在变量中传递C++11中的lambda函数

继续前面的例子,现要在process_integers()方法中使用一个lambda函数。

依然没有什么需要做的,因为boost::function<> 也可以使用任何难度的lambda函数

// 不带参数什么也不做的lambda函数
process_integers([](int /*i*/){});

// 存储一个引用的lambda函数
std::deque<int> ints;
process_integers([&ints](int i){
ints.push_back(i);
});

// 修改其内容的lamba函数
std::size_t match_count = 0;
process_integers([ints,&match_count](int i) mutable{
if (ints.front() == i)
{
++match_count;
}
ints.pop_front();
});


指针的容器

在容器中存储多态性数据,迫使容器中的数据快速复制,以及对容器内的数据操作有严格的异常需求时,我们需要在容器中存储指针,并使用delete操作符处理它们的析构:

#include <set>
#include <algorithm>
#include <boost/bind.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <cassert>

template <class T>
struct ptr_cmp :public std::binary_function<T, T, bool>
{
template <class T1>
bool operator()(const T1& v1, const T1& v2)const {
return operator()(*v1, *v2);
}
bool operator()(const T& v1, const T& v2) const {
return std::less<T>(*v1, *v2);
}
};

void example1()
{
std::set<int*, ptr_cmp<int>> s;
s.insert(new int(1));
s.insert(new int(0));

assert(**s.begin() == 0);

std::for_each(s.begin(), s.end(),
boost::bind(::operator delete, _1));
}


以上做法容易出错,可以考虑在容器中存放智能指针:

// 用于C++03版本
void example2_a()
{
typedef std::auto_ptr<int> int_aptr_t;
std::set<int_aptr_t, ptr_cmp<int>> s;
s.insert(int_aptr_t(new int(1)));
s.insert(int_aptr_t(new int(0)));

assert(**s.begin() == 0);

// 资源将被auto_ptr<>释放
}


然而,std::auto_ptr类已经过时,不推荐在容器中使用它,此外,这个例子将无法用C++11编译。

// 用于C++11版本
void example2_b()
{
typedef std::unique_ptr<int> int_uptr_t;
std::set<int_uptr_t, ptr_cmp<int>> s;
s.insert(int_uptr_t(new int(1)));
s.insert(int_uptr_t(new int(0)));

assert(**s.begin() == 0);

// 资源将被unique_ptr<>释放
}


一个很好的解决方案,但是它不能用于C++03,并且仍然要写一个比较器函数化对象。在容器中使用Boost.SmartPtr:

#include <boost/shared_ptr.hpp>

void example3()
{
typedef boost::shared_ptr<int> int_sptr_t;
std::set<int_sptr_t, ptr_cmp<int>> s;

s.insert(int_sptr_t(new int(1)));
s.insert(int_sptr_t(new int(0)));

assert(**s.begin() == 0);

// 资源将被shared_ptr<>释放
}


这个解决方案可移植,但仍然要写比较器,而这增加了性能损耗。Boost.PointerContainer库提供了一个良好的并可移植的解决方案:

#include <boost/ptr_container/ptr_set.hpp>

void correct_impl()
{
boost::ptr_set<int> s;
s.insert(new int(1));
s.insert(new int(0));

assert(*s.begin() == 0);

// 资源被容器本身释放
}


Boost.PointerContainer库拥有ptr_vector、ptr_array、ptr_set、ptr_multimap类和其他类。所有这些容器都能简化你的工作。在处理指针时,它们将在析构函数中被释放的指针,并简化对指针所指向的数据的访问。

在退出作用域时做一些事

#include <boost/scope_exit.hpp>
#include <cstdlib>
#include <cstdio>
#include <cassert>

int main()
{
std::FILE* f = std::fopen("example_file.txt", "w");
assert(f);
BOOST_SCOPE_EXIT(f)
{
// 无论作用域中发生什么,都将执行这段代码,并且将正确关闭文件
std::fclose(f);
}BOOST_SCOPE_EXIT_END

// ...
system("pause");
return 0;
}


为了在一个成员函数里面捕捉它,需要使用特殊符号this_:

class there_more_example
{
public:
void close(std::FILE*);
void theres_more_example_func()
{
std::FILE* f = 0;
BOOST_SCOPE_EXIT(f, this_)  // 捕获对象"this_"
{
this_->close(f);
}BOOST_SCOPE_EXIT_END
}
};


Boost.ScopeExit库不在堆上分配任何额外的内存,并且不使用虚函数。

用派生类的成员初始化基类

有这样一个例子,一个有虚函数的基类,它必须用std::ostream的对象的引用进行初始化,而它的派生类,有一个std::ostream的对象并实现了do_process()函数:

#include <boost/noncopyable.hpp>
#include <sstream>

class tasks_processor :boost::noncopyable
{
std::ostream& log;
protected:
virtual void do_process() = 0;
public:
explicit tasks_processor(std::ostream& log_) :log(log_)
{}

void process()
{
log << "Starting data processing";
do_process();
}
};

class fake_tasks_processor :public tasks_processor
{
std::ostringstream logger;

virtual void do_process()
{
logger << "Fake processor processed!";
}
public:
fake_tasks_processor()
:tasks_processor(logger)    // logger不在这里
, logger()
{}
};


由于直接基类在非静态数据成员之前进行初始化,无论成员初始化的顺序如何,所以我们不能控制在初始化logger之后再初始化基类。

Boost.Utility库提供了这种情况下的一个快速解决方案,称为boost::base_from_member模板。使用如下:

#include <boost/utility/base_from_member.hpp>

// 从boost::base_from_member<T>派生,其中T是必须在基类之前初始化的类型
// 注意顺序,boost::base_from_member<T>必须放在使用T的类前面
class fake_tasks_processor_fixed
:boost::base_from_member<std::ostringstream>
, public tasks_processor
{
typedef boost::base_from_member<std::ostringstream> logger_t;
// ...
public:
fake_tasks_processor_fixed()
: logger_t()
,tasks_processor(logger_t::member)
{}
};


工作原理

如果直接基类在非静态数据成员之前初始化,并且如果直接基类将按它们出现在基类指定列表的声明顺序初始化,我们需要以某种方式使一个基类成为非静态数据成员,或者制作一个基类,它有一个成员字段带有所需的成员:

template < typename MemberType, int UniqueID = 0 >
class base_from_member
{
protected:
MemberType  member;
// 构造函数...
};


base_from_member有一个整数作为第二个模板参数,为了解决我们需要多个相同类型的base_from_member类的问题。base_from_member类既不采用额外的动态内存分配,也没有虚函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: