在本章中,我们将涵盖以下主题:
- 管理指向不离开作用域的类的本地指针
- 跨函数使用的类指针的引用计数
- 管理指向不超出范围的数组的本地指针
- 跨函数使用的数组指针的引用计数
- 将任何功能对象存储在变量中
- 在变量中传递函数指针
- 在变量中传递 C++ 11 lambda 函数
- 指针容器
- 在示波器出口处进行!
- 由派生类的成员初始化基类
在本章中,我们将继续讨论 Boost 库引入的数据类型,主要集中在处理指针上。我们将看到如何轻松管理资源,如何使用能够存储任何函数对象、函数和 lambda 表达式的数据类型。读完这一章,你的代码将变得更加可靠,内存泄漏将成为历史。
有时,我们需要动态分配内存,并在内存中构造一个类。这就是麻烦的开始。看看下面的代码:
bool foo1() {
foo_class* p = new foo_class("Some data");
const 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
就不会被删除了。让我们用下面的方法修复它:
bool foo2() {
foo_class* p = new foo_class("Some data");
try {
const bool something_else_happened = some_function1(*p);
if (something_else_happened) {
delete p;
return false;
}
some_function2(p);
} catch (...) {
delete p;
throw;
}
delete p;
return true;
}
现在代码是正确的,但是很难看,很难读。我们能做得更好吗?
需要 C++ 和异常期间代码行为的基本知识。
看看Boost.SmartPtr
图书馆就知道了。有一门boost::scoped_ptr
课可能会帮到你:
#include <boost/scoped_ptr.hpp>
bool foo3() {
const boost::scoped_ptr<foo_class> p(new foo_class("Some data"));
const bool something_else_happened = some_function1(*p);
if (something_else_happened) {
return false;
}
some_function2(p.get());
return true;
}
现在,资源没有泄露的可能,源代码更加清晰。
If you have control over some_function2(foo_class*)
, you may wish to rewrite it to take a reference to foo_class
instead of a pointer. An interface with references is more intuitive than an interface with pointers unless you have a special agreement in your company that output parameters are taken only by pointer.
顺便说一下,Boost.Move
还有一个boost::movelib::unique_ptr
你可以用来代替boost::scoped_ptr
:
#include <boost/move/make_unique.hpp>
bool foo3_1() {
const boost::movelib::unique_ptr<foo_class> p
= boost::movelib::make_unique<foo_class>("Some data");
const bool something_else_happened = some_function1(*p);
if (something_else_happened) {
return false;
}
some_function2(p.get());
return true;
}
boost::scoped_ptr<T>
和boost::movelib::unique_ptr
是典型的 RAII 类。当抛出异常或变量超出范围时,堆栈被展开,析构函数被调用。在析构函数中,scoped_ptr<T>
和unique_ptr<T>
为它们存储的指针调用delete
。因为这两个类默认都调用delete
,所以如果基类的析构函数是虚拟的,那么通过指向base
类的指针来保存derived
类是安全的:
#include <iostream>
#include <string>
struct base {
virtual ~base(){}
};
class derived: public base {
std::string str_;
public:
explicit derived(const char* str)
: str_(str)
{}
~derived() /*override*/ {
std::cout << "str == " << str_ << '\n';
}
};
void base_and_derived() {
const boost::movelib::unique_ptr<base> p1(
boost::movelib::make_unique<derived>("unique_ptr")
);
const boost::scoped_ptr<base> p2(
new derived("scoped_ptr")
);
}
运行base_and_derived()
功能将产生以下输出:
str == scoped_ptr
str == unique_ptr
In C++, destructors for objects are called in the reverse construction order. That's why the destructor of scoped_ptr
was called before the destructor of unique_ptr
.
boost::scoped_ptr<T>
类模板既不可复制也不可移动。boost::movelib::unique_ptr
类是一个只移动的类,它在 C++ 11 之前的编译器上使用移动仿真。这两个类都存储一个指向它们拥有的资源的指针,并且不要求T
是一个完整的类型(T
可以被正向声明)。
某些编译器在删除不完整的类型时不会发出警告,这可能会导致难以检测的错误。幸运的是,在这种情况下,具有特定编译时断言的 Boost 类不是这种情况。这使得scoped_ptr
和unique_ptr
非常适合实现皮条客成语:
// In header file:
struct public_interface {
// ...
private:
struct impl; // Forward declaration.
boost::movelib::unique_ptr<impl> impl_;
};
那些课非常快。编译器将使用scoped_ptr
和unique_ptr
的代码优化为机器代码,与手写的手动内存管理代码相比,这不涉及额外的开销。
C++ 11 有一个std::unique_ptr<T, D>
类,它唯一拥有资源,并且行为与boost::movelib::unique_ptr<T, D>
完全一样。
c++ 标准库没有boost::scoped_ptr<T>
,但是你可以用const std::unique_ptr<T>
来代替。唯一不同的是boost::scoped_ptr<T>
和const std::unique_ptr<T>
不一样,还是可以叫reset()
。
Boost.SmartPtr
库的文档包含了许多关于所有智能指针类的例子和其他有用信息。你可以在http://boost.org/libs/smart_ptr.读到他们- 如果你使用
boost::movelib::unique_ptr
和的移动模拟,这些Boost.Move
文档可能会帮助你
假设您有一些包含数据的动态分配的结构,并且您希望在不同的执行线程中处理它。这样做的代码如下:
#include <boost/thread.hpp>
#include <boost/bind.hpp>
void process1(const foo_class* p);
void process2(const foo_class* p);
void process3(const foo_class* p);
void foo1() {
while (foo_class* p = get_data()) // C way
{
// There will be too many threads soon, see
// recipe 'Parallel execution of different tasks'
// for a good way to avoid uncontrolled growth of threads
boost::thread(boost::bind(&process1, p))
.detach();
boost::thread(boost::bind(&process2, p))
.detach();
boost::thread(boost::bind(&process3, p))
.detach();
// delete p; Oops!!!!
}
}
我们不能在while
循环结束时释放p
,因为它仍然可以被运行process
函数的线程使用。这些process
函数不能删除p
,因为它们不知道其他线程不再使用它了。
本食谱使用Boost.Thread
库,它不是一个只包含标题的库。您的程序必须链接到boost_thread
、boost_chrono
和boost_system
库。在进一步阅读之前,确保你理解线程的概念。参考参见部分,了解描述螺纹的配方。
你还需要一些关于boost::bind
或者std::bind
的基础知识,基本都差不多。
您可能已经猜到,Boost(和 C++ 11)中有一个类可能会帮助您处理这个问题。叫做boost::shared_ptr
。它可以如下使用:
#include <boost/shared_ptr.hpp>
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 foo2() {
typedef boost::shared_ptr<foo_class> ptr_t;
ptr_t p;
while (p = ptr_t(get_data())) // C way
{
boost::thread(boost::bind(&process_sp1, p))
.detach();
boost::thread(boost::bind(&process_sp2, p))
.detach();
boost::thread(boost::bind(&process_sp3, p))
.detach();
// no need to anything
}
}
这方面的另一个例子如下:
#include <string>
#include <boost/smart_ptr/make_shared.hpp>
void process_str1(boost::shared_ptr<std::string> p);
void process_str2(const boost::shared_ptr<std::string>& p);
void foo3() {
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'))"
);
boost::thread(boost::bind(&process_str1, ps))
.detach();
boost::thread(boost::bind(&process_str2, ps))
.detach();
}
shared_ptr
类内部有一个原子引用计数器。复制时,引用计数器递增,调用其destructor
时,引用计数器递减。当引用计数等于零时,shred_ptr
指向的对象调用delete
。
现在,让我们来看看boost::thread (boost::bind(&process_sp1, p))
的情况下发生了什么。函数process_sp1
以一个参数作为参考,那么当我们退出while
循环时,为什么不释放它呢?答案很简单。bind()
返回的功能对象包含一个shared
指针的副本,这意味着p
指向的数据在功能对象被销毁之前不会被解除分配。功能对象被复制到线程中,并保持活动状态,直到线程执行。
回到boost::make_shared
,我们来看看shared_ptr<std::string> ps(new int(0))
。在这种情况下,我们有两个电话打给new
:
- 通过
new int(0)
构造一个整数指针时 - 当构造在堆上分配的
shared_ptr
类内部引用计数器时
使用make_shared<T>
只能对new
进行一次呼叫。一个make_shared<T>
分配一个内存块,并在该内存块中构造一个原子计数器和T
对象。
原子引用计数器保证了线程间shared_ptr
的正确行为,但是你必须记住原子操作没有非原子操作快。shared_ptr
在赋值、复制构造和销毁未被移走的shared_ptr
时触及原子变量。这意味着在 C++ 11 兼容的编译器上,您可以尽可能使用移动构造和移动赋值来减少原子操作的数量。如果不再使用p
变量,就使用shared_ptr<T> p1(std::move(p))
。如果不打算修改指向值,建议做成const
。只需将const
添加到智能指针的模板参数中,编译器会确保您不会修改内存:
void process_cstr1(boost::shared_ptr<const std::string> p);
void process_cstr2(const boost::shared_ptr<const std::string>& p);
void foo3_const() {
boost::shared_ptr<const std::string> ps
= boost::make_shared<const std::string>(
"Some immutable string"
);
boost::thread(boost::bind(&process_cstr1, ps))
.detach();
boost::thread(boost::bind(&process_cstr2, ps))
.detach();
// *ps = "qwe"; // Compile time error, string is const!
}
Confused with const
? Here's a mapping of smart pointer constness to simple pointer constness:
| shared_ptr<T>
| T*
|
| shared_ptr<const T>
| const T*
|
| const shared_ptr<T>
| T* const
|
| const shared_ptr<const T>
| const T* const
|
shared_ptr
调用和make_shared
函数是 C++ 11 的一部分,它们在std::
命名空间的头<memory>
中声明。它们具有与 Boost 版本几乎相同的特性。
- 有关
Boost.Thread
和原子操作的更多信息,请参考第 5 章**【多线程】。 ** 有关Boost.Bind
的更多信息,请参考第 1 章开始编写应用中的绑定和重新排序功能参数配方。* 有关如何将shared_ptr<U>
转换为shared_ptr<T>
的信息,请参考第 3 章、配方转换智能指针。*Boost.SmartPtr
库的文档包含了许多关于所有智能指针类的例子和其他有用信息。参考链接http://boost.org/libs/smart_ptr了解他们。*
*# 管理指向不超出范围的数组的指针
我们已经在中看到了如何管理指向资源的指针,管理指向不离开范围的类的指针。但是,当我们处理数组时,我们需要调用delete[]
而不是简单的delete
。否则,会出现内存泄漏。看看下面的代码:
void may_throw1(char ch);
void may_throw2(const char* buffer);
void foo() {
// we cannot allocate 10MB of memory on stack,
// so we allocate it on heap
char* buffer = new char[1024 * 1024 * 10];
// Oops. Here comes some code, that may throw.
// It was a bad idea to use raw pointer as the memory may leak!!
may_throw1(buffer[0]);
may_throw2(buffer);
delete[] buffer;
}
这个食谱需要 C++ 异常和模板的知识。
Boost.SmartPointer
图书馆不仅有scoped_ptr<>
班,还有一个scoped_array<>
班:
#include <boost/scoped_array.hpp>
void foo_fixed() {
// We allocate array on heap
boost::scoped_array<char> buffer(new char[1024 * 1024 * 10]);
// Here comes some code, that may throw,
// but now exception won't cause a memory leak
may_throw1(buffer[0]);
may_throw2(buffer.get());
// destructor of 'buffer' variable will call delete[]
}
Boost.Move
库的boost::movelib::unique_ptr<>
类也可以处理数组。您只需要通过在模板参数的末尾提供[]
来指示它正在存储一个数组:
#include <boost/move/make_unique.hpp>
void foo_fixed2() {
// We allocate array on heap
const boost::movelib::unique_ptr<char[]> buffer
= boost::movelib::make_unique<char[]>(1024 * 1024 * 10);
// Here comes some code, that may throw,
// but now exception won't cause a memory leak
may_throw1(buffer[0]);
may_throw2(buffer.get());
// destructor of 'buffer' variable will call delete[]
}
scoped_array<>
的工作方式和scoped_ptr<>
类完全一样,但是在析构函数中调用delete[]
而不是delete
。unique_ptr<T[]>
做同样的事情。
scoped_array<>
级和scoped_ptr<>
级有相同的保证和设计。它既没有额外的内存分配,也没有虚函数调用。它不能被复制,也不是 C++ 11 的一部分。std::unique_ptr<T[]>
是 C++ 11 的一部分,具有与boost::movelib::unique_ptr<T[]>
类相同的保证和性能。
Actually, make_unique<char[]>(1024)
is not the same as new char[1024]
, because the first one does value initialization and the second one does the default initialization. The equivalent function for default-initialization is boost::movelib::make_unique_definit
.
请注意,Boost 版本也可以在 C++ 11 之前的编译器上工作,甚至可以在其上模拟右值,使boost::movelib::unique_ptr
成为只移动的类型。如果您的标准库不提供std::make_unique
,那么Boost.SmartPtr
可能会帮助您。它提供了在标题boost/smart_ptr/make_unique.hpp
中返回一个std::unique_ptr
的boost::make_unique
。它还在同一标题中提供boost::make_unique_noinit
进行默认初始化。C++ 17 没有make_unique_noinit
函数。
Using new
for memory allocation and manual memory management in C++ is a bad habit. Use make_unique
and make_shared
functions wherever possible.
Boost.SmartPtr
库的文档包含了许多关于所有智能指针类的例子和其他有用的信息,你可以在http://boost.org/libs/smart_ptr.上读到它们- 如果你想使用
boost::movelib::unique_ptr
的移动模拟,这些Boost.Move
文档可能会帮助你,在http://boost.org/libs/move.T4 阅读它们
我们继续处理指针,下一个任务是引用计数数组。让我们看一下从流中获取一些数据并在不同线程中处理它的程序。这样做的代码如下:
#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) {
// We need to copy data, because we do not know,
// when it will be deallocated by the caller.
char* data_cpy = new char[size];
std::memcpy(data_cpy, data, size);
// Starting thread of execution to process data.
boost::thread(boost::bind(&do_process, data_cpy, size))
.detach();
boost::thread(boost::bind(&do_process, data_cpy, size))
.detach();
// Oops!!! We cannot delete[] data_cpy, because
// do_process() function may still work with it.
}
与引用计数跨函数使用的类的指针配方中出现的问题相同。
这个食谱使用Boost.Thread
库,它不是一个只有标题的库,所以你的程序需要链接到boost_thread
、boost_chrono
和boost_system
库。在进一步阅读之前,确保你理解线程的概念。
你还需要一些关于boost::bind
或std::bind
的基本知识,这几乎是一样的。
有四种解决方案。它们之间的主要区别在于data_cpy
变量的类型和构造。所有这些解决方案都做了与本食谱开头所述完全相同的事情,但没有内存泄漏。解决方案如下:
- 第一种解决方案适用于在编译时已知数组大小的情况:
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
template <std::size_t Size>
void do_process_shared(const boost::shared_ptr<char[Size]>& data);
template <std::size_t Size>
void do_process_in_background_v1(const char* data) {
// Same speed as in 'First solution'.
boost::shared_ptr<char[Size]> data_cpy
= boost::make_shared<char[Size]>();
std::memcpy(data_cpy.get(), data, Size);
// Starting threads of execution to process data.
boost::thread(boost::bind(&do_process_shared<Size>, data_cpy))
.detach();
boost::thread(boost::bind(&do_process_shared<Size>, data_cpy))
.detach();
// data_cpy destructor will deallocate data when
// reference count is zero.
}
- 由于 Boost 1.53,
shared_ptr
本身可以处理未知界的数组。第二种解决方案:
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
void do_process_shared_ptr(
const boost::shared_ptr<char[]>& data,
std::size_t size);
void do_process_in_background_v2(const char* data, std::size_t size) {
// Faster than 'First solution'.
boost::shared_ptr<char[]> data_cpy = boost::make_shared<char[]>(size);
std::memcpy(data_cpy.get(), data, size);
// Starting threads of execution to process data.
boost::thread(boost::bind(&do_process_shared_ptr, data_cpy, size))
.detach();
boost::thread(boost::bind(&do_process_shared_ptr, data_cpy, size))
.detach();
// data_cpy destructor will deallocate data when
// reference count is zero.
}
- 第三种解决方案:
#include <boost/shared_ptr.hpp>
void do_process_shared_ptr2(
const boost::shared_ptr<char>& data,
std::size_t size);
void do_process_in_background_v3(const char* data, std::size_t size) {
// Same speed as in 'First solution'.
boost::shared_ptr<char> data_cpy(
new char[size],
boost::checked_array_deleter<char>()
);
std::memcpy(data_cpy.get(), data, size);
// Starting threads of execution to process data.
boost::thread(boost::bind(&do_process_shared_ptr2, data_cpy, size))
.detach();
boost::thread(boost::bind(&do_process_shared_ptr2, data_cpy, size))
.detach();
// data_cpy destructor will deallocate data when
// reference count is zero.
}
- 自 Boost 1.65 以来,最后一种解决方案已被弃用,但在古董 Boost 版本中可能有用:
#include <boost/shared_array.hpp>
void do_process_shared_array(
const boost::shared_array<char>& data,
std::size_t size);
void do_process_in_background_v4(const char* data, std::size_t size) {
// We need to copy data, because we do not know, when it will be
// deallocated by the caller.
boost::shared_array<char> data_cpy(new char[size]);
std::memcpy(data_cpy.get(), data, size);
// Starting threads of execution to process data.
boost::thread(
boost::bind(&do_process_shared_array, data_cpy, size)
).detach();
boost::thread(
boost::bind(&do_process_shared_array, data_cpy, size)
).detach();
// No need to call delete[] for data_cpy, because
// data_cpy destructor will deallocate data when
// reference count is zero.
}
在所有示例中,智能指针类对引用进行计数,并在引用计数等于零时调用delete[]
获取指针。第一个和第二个例子很简单。在第三个例子中,我们为一个shared
指针提供了一个自定义的deleter
对象。当智能指针决定释放资源时,调用智能指针的deleter
对象。当智能指针在没有显式deleter
的情况下被构造时,默认的deleter
被构造为根据智能指针的模板类型调用delete
或delete[]
。
第四个解决方案是最保守的,因为在 Boost 1.53 之前,第二个解决方案的功能没有在shared_ptr
中实现。第一个和第二个解决方案是最快的,因为它们只使用一个内存分配调用。第三种解决方案可以用于较旧版本的 Boost 和 C++ 11 标准库的std::shared_ptr<>
(只是别忘了将boost::checked_array_deleter<T>()
改为std::default_delete<T[]>()
)。
Actually, boost::make_shared<char[]>(size)
is not the same as new char[size]
, because it involves value-initialization of all elements. The equivalent function for default-initialization is boost::make_shared_noinit
.
当心!std::shared_ptr
的 C++ 11 和 C++ 14 版本不能用数组!只是既然 C++ 17 std::shared_ptr<T[]>
必须正常工作。如果您计划编写可移植代码,请考虑使用boost::shared_ptr
、boost::shared_array
,或者明确地将一个deleter
传递给std::shared_ptr
。
boost::shared_ptr<T[]>
, boost::shared_array
, and C++ 17 std::shared_ptr<T[]>
have operator[](std::size_t index)
that allows you to access elements of shared array by index. boost::shared_ptr<T>
and std::shared_ptr<T>
with custom deleter
have no operator[]
, which makes them less useful.
Boost.SmartPtr
库的文档包含了许多关于所有智能指针类的例子和其他有用的信息。你可以在http://boost.org/libs/smart_ptr看到。
当您开发一个在头文件中声明了 API 并在源文件中实现的库时,请考虑这种情况。该库应具有接受任何功能对象的功能。看看下面的代码:
// making a typedef for function pointer accepting int
// and returning nothing
typedef void (*func_t)(int);
// Function that accepts pointer to function and
// calls accepted function for each integer that it has.
// It cannot work with functional objects :(
void process_integers(func_t f);
// Functional object
class int_processor {
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
功能接受任何功能对象?
建议在开始使用此配方之前,阅读第 1 章、中的【容器/变量】配方中存储的任何值,开始编写您的应用*。*
有一个解决方案,叫做Boost.Function
库。它允许您存储任何函数、成员函数或函数对象,如果它的签名与模板参数中描述的匹配:
#include <boost/function.hpp>
typedef boost::function<void(int)> fobject_t;
// Now this function may accept functional objects
void process_(const fobject_t& f);
int main() {
bool is_triggered = false;
int_processor fo(0, 200, is_triggered);
process_integers(fo);
assert(is_triggered);
}
fobject_t
对象本身存储功能对象,并删除它们的确切类型。将boost::function
用于有状态对象是安全的:
bool g_is_triggered = false;
void set_functional_object(fobject_t& f) {
// Local variable
int_processor fo( 100, 200, g_is_triggered);
f = fo;
// now 'f' holds a copy of 'fo'
// 'fo' leavs scope and will be destroyed,
// but it's OK to use 'f' in outer scope.
}
boost::function
是否回忆起boost::any
班?这是因为它使用相同的技术类型擦除来存储任何功能对象。
boost::function
类有一个默认的构造函数,并且有一个空状态。检查空/默认构造状态可以这样完成:
void foo(const fobject_t& f) {
// boost::function is convertible to bool
if (f) {
// we have value in 'f'
// ...
} else {
// 'f' is empty
// ...
}
}
Boost.Function
库有大量疯狂的优化。它可以存储小的功能对象,而无需额外的内存分配,并且具有优化的移动分配操作符。它被接受为 C++ 11 标准库的一部分,并在std::
命名空间的<functional>
头中定义。
boost::function
对存储在其中的对象使用 RTTI。如果禁用 RTTI,该库将继续工作,但会显著增加编译后的二进制文件的大小。
Boost.Function
的官方文档包含更多示例、性能度量和类参考文档。参考链接http://boost.org/libs/function了解。- 变量配方中的传递函数指针。
- 在变量配方中传递 c++ 11λ函数。
我们继续前面的例子,现在我们想在我们的process_integers()
方法中传递一个指向函数的指针。我们应该仅仅为函数指针添加一个重载,还是有更好的方法?
这个食谱延续了上一个食谱。你必须先阅读之前的食谱。
不需要做任何事情,因为boost::function<>
也可以从函数指针中构造:
void my_ints_function(int i);
int main() {
process_integers(&my_ints_function);
}
指向my_ints_function
的指针将存储在boost::function
类中,对boost::function
的调用将被转发到存储的指针。
Boost.Function
库为指向函数的指针提供了良好的性能,不会在堆上分配内存。标准库std::function
在存储函数指针方面也很有效。从 Boost 1.58 开始,Boost.Function
库可以存储带有右值引用的调用签名的函数和函数对象:
boost::function<int(std::string&&)> f = &something;
f(std::string("Hello")); // Works
Boost.Function
的官方文档包含更多示例、性能度量和类参考文档。跟随http://boost.org/libs/function阅读相关内容。- 在变量配方中传递 c++ 11λ函数。
我们继续前面的例子,现在我们想在我们的process_integers()
方法中使用一个 lambda 函数。
这个食谱是前两个系列的延续。你必须先读它们。您还需要一个兼容 C++ 11 的编译器,或者至少一个支持 C++ 11 lambda 的编译器。
不需要做任何事情,因为boost::function<>
也可以用于任何难度的λ函数:
#include <deque>
//#include "your_project/process_integers.h"
void sample() {
// lambda function with no parameters that does nothing
process_integers([](int /*i*/){});
// lambda function that stores a reference
std::deque<int> ints;
process_integers([&ints](int i){
ints.push_back(i);
});
// lambda function that modifies its content
std::size_t match_count = 0;
process_integers([ints, &match_count](int i) mutable {
if (ints.front() == i) {
++ match_count;
}
ints.pop_front();
});
}
Boost.Functional
中 lambda 函数存储的性能与其他情况相同。虽然由 lambda 表达式生成的函数对象足够小,可以放入boost::function
的实例中,但不会执行动态内存分配。调用存储在boost::function
中的对象的速度接近指针调用函数的速度。复制boost::function
仅当初始boost::function
有一个存储对象不适合它而没有分配时才分配堆内存。移动实例不会分配和释放内存。
请记住boost::function
意味着编译器的优化障碍。这意味着:
std::for_each(v.begin(), v.end(), [](int& v) { v += 10; });
通常比以下更能被编译器优化:
const boost::function<void(int&)> f0(
[](int& v) { v += 10; }
);
std::for_each(v.begin(), v.end(), f0);
这就是为什么在实际不需要使用Boost.Function
时,要尽量避免使用的原因。在某些情况下,C++ 11 auto
关键字反而很方便:
const auto f1 = [](int& v) { v += 10; };
std::for_each(v.begin(), v.end(), f1);
关于性能和Boost.Function
的更多信息可以在位于http://www.boost.org/libs/function的官方文档页面上找到。
当我们需要在容器中存储指针时,就会出现这种情况。例如:将多态数据存储在容器中,强制容器中数据的快速复制,以及对容器中数据操作的严格异常要求。在这种情况下,C++ 程序员有以下选择:
- 将指针存储在容器中,并使用
delete
处理它们的析构:
#include <set>
#include <algorithm>
#include <cassert>
template <class T>
struct ptr_cmp {
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);
// ...
// Oops! Any exception in the above code leads to
// memory leak.
// Deallocating resources.
std::for_each(s.begin(), s.end(), [](int* p) { delete p; });
}
这种方法容易出错,需要大量的编写工作
- 在容器中存储 C++ 11 智能指针:
#include <memory>
#include <set>
void example2_cpp11() {
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);
// ...
// Resources will be deallocated by unique_ptr<>.
}
这个解决方案很好,但是不能用在 C++ 03 中,还需要写一个比较器函数对象。
C++ 14 has a std::make_unique
function for construction of std::uniue_ptr
s. Using it instead of new
is a good coding style!
- 在容器中使用
Boost.SmartPtr
:
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
void example3() {
typedef boost::shared_ptr<int> int_sptr_t;
std::set<int_sptr_t, ptr_cmp<int> > s;
s.insert(boost::make_shared<int>(1));
s.insert(boost::make_shared<int>(0));
// ...
assert(**s.begin() == 0);
// ...
// Resources will be deallocated by 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);
// ...
// Resources will be deallocated by container itself.
}
Boost.PointerContainer
库有类ptr_array
、ptr_vector
、ptr_set
、ptr_multimap
等。这些类根据需要释放指针,并简化对指针所指向的数据的访问(不需要在assert(*s.begin() == 0);
中进行额外的取消引用)。
当我们想要克隆一些数据时,需要在被克隆对象的命名空间中定义一个独立的函数T*new_clone(const T& r)
。此外,如果包含头文件<boost/ptr_container/clone_allocator.hpp>
,可以使用默认的T* new_clone(const T& r)
实现,如下面的代码所示:
#include <boost/ptr_container/clone_allocator.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <cassert>
void theres_more_example() {
// Creating vector of 10 elements with values 100
boost::ptr_vector<int> v;
int value = 100;
v.resize(10, &value); // Beware! No ownership of pointer!
assert(v.size() == 10);
assert(v.back() == 100);
}
C++ 标准库没有指针容器,但是使用std::unique_ptr
的容器可以实现同样的功能。对了,从 Boost 1.58 开始,有一个boost::movelib::unique_ptr
类可以在 C++ 03 中使用。您可以将其与来自Boost.Container
库的容器混合,以获得存储指针的 C++ 11 功能:
#include <boost/container/set.hpp>
#include <boost/move/make_unique.hpp>
#include <cassert>
void example2_cpp03() {
typedef boost::movelib::unique_ptr<int> int_uptr_t;
boost::container::set<int_uptr_t, ptr_cmp<int> > s;
s.insert(boost::movelib::make_unique<int>(1));
s.insert(boost::movelib::make_unique<int>(0));
// ...
assert(**s.begin() == 0);
}
Not all the developers know the Boost libraries well. It is more developer-friendly to use functions and classes that have C++ standard library alternatives, as the developers usually are more aware of the standard library features. So if there's no big difference for you, use Boost.Container
with boost::movelib::unique_ptr
.
- 官方文档包含每堂课的详细参考,点击链接http://boost.org/libs/ptr_container阅读。
- 本章的前四个食谱给你一些关于智能指针用法的例子。
- 第 9 章 、容器中的多个食谱描述了
Boost.Container
库的特点。看看那一章,寻找酷的、有用的、快速的容器。
如果你在处理语言,如 Java、C#或 Delphi,你显然是在使用try {} finally{}
结构。让我简单地给你描述一下这些语言结构是做什么的。
当程序通过返回或异常离开当前范围时,执行finally
块中的代码。这种机制用于替代 RAII 模式:
// Some pseudo code (suspiciously similar to Java code)
try {
FileWriter f = new FileWriter("example_file.txt");
// Some code that may throw or return
// ...
} finally {
// Whatever happened in scope, this code will be executed
// and file will be correctly closed
if (f != null) {
f.close()
}
}
在 C++ 中有没有办法做到这样的事情?
这个食谱需要基本的 C++ 知识。了解抛出异常期间的代码行为将受到赞赏。
C++ 使用 RAII 模式而不是try {} finally{}
构造。Boost.ScopeExit
库被设计成允许用户在函数体中定义 RAII 包装:
#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) {
// Whatever happened in outer scope, this code will be executed
// and file will be correctly closed.
std::fclose(f);
} BOOST_SCOPE_EXIT_END
// Some code that may throw or return.
// ...
}
f
通过BOOST_SCOPE_EXIT(f)
传递给数值。当程序离开执行范围时,BOOST_SCOPE_EXIT(f) {
和} BOOST_SCOPE_EXIT_END
之间的代码被执行。如果我们希望通过引用传递该值,请使用BOOST_SCOPE_EXIT
宏中的&
符号。如果我们希望传递多个值,只需用逗号分隔它们。
Passing references to a pointer does not work well on some compilers. The BOOST_SCOPE_EXIT(&f)
macro cannot be compiled there, which is why we do not capture it by reference in the example.
为了在成员函数中捕捉这一点,我们将使用一个特殊的符号this_
:
class theres_more_example {
public:
void close(std::FILE*);
void theres_more_example_func() {
std::FILE* f = 0;
BOOST_SCOPE_EXIT(f, this_) { // Capturing `this` as 'this_'
this_->close(f);
} BOOST_SCOPE_EXIT_END
}
};
Boost.ScopeExit
库不在堆上分配额外的内存,也不使用虚函数。使用默认语法,不要定义BOOST_SCOPE_EXIT_CONFIG_USE_LAMBDAS
,因为否则将使用boost::function
实现作用域退出,这可能会分配额外的内存并暗示优化障碍。通过指定自定义的deleter
,使用boost::movelib::unique_ptr
或std::unique_ptr
可以获得接近BOOST_SCOPE_EXIT
的结果:
#include <boost/move/unique_ptr.hpp>
#include <cstdio>
void unique_ptr_example() {
boost::movelib::unique_ptr<std::FILE, int(*)(std::FILE*)> f(
std::fopen("example_file.txt", "w"), // open file
&std::fclose // specific deleter
);
// ...
}
If you write two or more similar bodies for BOOST_SCOPE_EXIT
, then it's time to think about some refactoring and moving the code to a fully functional RAII class.
官方文档包含更多的例子和用例。你可以在http://boost.org/libs/scope_exit.看到
让我们看看下面的例子。我们有一些基类,它有虚函数,必须参照std::ostream
对象进行初始化:
#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();
}
};
我们还有一个派生类,它有一个std::ostream
对象并实现了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_) // Oops! logger_ does not exist here
, logger_()
{}
};
这在编程中并不是一个很常见的情况,但是当这样的错误发生时,想要绕过它并不总是那么简单。有些人试图通过改变logger_
的顺序和基类型初始化来绕过它:
fake_tasks_processor()
: logger_() // Oops! It is still constructed AFTER tasks_processor.
, tasks_processor(logger_)
{}
它不会像预期的那样工作,因为直接基类是在非静态数据成员之前初始化的,不管成员初始值设定项的顺序如何。
这个食谱需要 C++ 的基础知识。
Boost.Utility
库为这种情况提供了快速解决方案。解决方案称为boost::base_from_member
模板。要使用它,您需要执行以下步骤:
- 包括
base_from_member.hpp
标题:
#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;
virtual void do_process() {
logger_t::member << "Fake processor processed!";
}
public:
fake_tasks_processor_fixed()
: logger_t()
, tasks_processor(logger_t::member)
{}
};
直接基类在非静态数据成员之前初始化,并且按照它们出现在基说明符列表中的声明顺序初始化。如果我们需要用某物初始化基类B
,我们需要使那个某物成为在B
之前声明的基类A
的一部分。换句话说,boost::base_from_member
只是一个简单的类,它将其模板参数保存为非静态数据成员:
template < typename MemberType, int UniqueID = 0 >
class base_from_member {
protected:
MemberType member;
// Constructors go there...
};
如您所见,base_from_member
有一个整数作为第二个模板参数。这是针对我们需要多个相同类型的base_from_member
类的情况:
class fake_tasks_processor2
: boost::base_from_member<std::ostringstream, 0>
, boost::base_from_member<std::ostringstream, 1>
, public tasks_processor
{
typedef boost::base_from_member<std::ostringstream, 0> logger0_t;
typedef boost::base_from_member<std::ostringstream, 1> logger1_t;
virtual void do_process() {
logger0_t::member << "0: Fake processor2 processed!";
logger1_t::member << "1: Fake processor2 processed!";
}
public:
fake_tasks_processor2()
: logger0_t()
, logger1_t()
, tasks_processor(logger0_t::member)
{}
};
boost::base_from_member
类既不应用额外的动态内存分配,也没有虚拟函数。如果您的编译器支持的话,当前的实现确实支持完美转发和变量模板。
C++ 标准库没有base_from_member
。
Boost.Utility
库包含很多有用的类和函数;获取更多信息的文档位于http://boost.org/libs/utility- 在第 1 章开始编写你的应用中的制作非可复制类食谱包含了更多来自
Boost.Utility
的类的例子 - 此外,第 1 章、开始编写应用中的使用 C++ 11 移动仿真方法包含更多来自
Boost.Utility
的类示例*