Skip to content

Latest commit

 

History

History
1183 lines (854 loc) · 42.5 KB

File metadata and controls

1183 lines (854 loc) · 42.5 KB

二、管理资源

在本章中,我们将涵盖以下主题:

  • 管理指向不离开作用域的类的本地指针
  • 跨函数使用的类指针的引用计数
  • 管理指向不超出范围的数组的本地指针
  • 跨函数使用的数组指针的引用计数
  • 将任何功能对象存储在变量中
  • 在变量中传递函数指针
  • 在变量中传递 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_ptrunique_ptr非常适合实现皮条客成语:

// In header file:
struct public_interface {
    // ...
private:
    struct impl; // Forward declaration.
    boost::movelib::unique_ptr<impl> impl_;
};

还有更多...

那些课非常快。编译器将使用scoped_ptrunique_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_threadboost_chronoboost_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[]而不是deleteunique_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_ptrboost::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_threadboost_chronoboost_system库。在进一步阅读之前,确保你理解线程的概念。

你还需要一些关于boost::bindstd::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被构造为根据智能指针的模板类型调用deletedelete[]

还有更多...

第四个解决方案是最保守的,因为在 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_ptrboost::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λ函数。

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

我们继续前面的例子,现在我们想在我们的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_ptrs. 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_arrayptr_vectorptr_setptr_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_ptrstd::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模板。要使用它,您需要执行以下步骤:

  1. 包括base_from_member.hpp标题:
#include <boost/utility/base_from_member.hpp>
  1. 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
  1. 正确地,按如下方式编写构造函数:
{
    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的类示例*