C++ Exceptional 最小化编译期依赖

首先,为什么要最小化编译器依赖呢?因为这样简化文件之间的依赖关系,减少由于一个文件的改变,而引起的另外的文件的重新编译。从而提高编译速度。下面来看一下下面的一个示例代码:

// x.h: original header

#include <iostream>
#include <ostream>
#include <list>
// None of A, B, C, D or E are templates.
// only A and C have virtual functions.
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
#include "e.h" // class E
class X : public A, private B
{
public:
    X(const C&);
    B f(int, char*);
    C f(int, C);
    C& g(B);
    E h(E);
    virtual std::ostream& print(std::osteam&) const;
private:
    std::list<C> clist_;
    D            d_;
};
inline std::ostream& operator<<(std::ostream& os, const X& x)
{
    return x.print(os);
}

下面分别列举一下以上代码中的问题:
1. 移除#include <iostream> 因为这里并不需要
2. 把#include <ostream> 替换成#include <iosfwd> 虽然我们的代码中用到了std::osteam,但程序中都是作为参数的,所以并不需要包含ostream, 只需要包含C++标准库提供给我们的类型声明头文件iosfwd 即可。
3. 移除#include "e.h",我们只需要前置声明一下就可以了,class E
4. 利用Pimpl-idiom 来进一步减少文件之间的依赖关系。
具体如下:

// x.h: after converting to use a Pimpl
// to hide implementation details

#include <iosfwd>
#include "a.h"
#include "b.h"
class C;
class E;

class X : public A, private B
{
public:
    X(const C&);
    B f(int, char*);
    C f(int, C);
    C& g(B);
    E h(E);
    virtual std::ostream& print(std::osteam&) const;
private:
    struct XImpl;
    XImpl* pimpl_;
};
inline std::ostream& operator<<(std:ostream& os, const X& x)
{
    return x.print(os);
}

//implementation file x.cpp
struct X::XImpl
{
    std::list<C> clist_;
    D            d_;
};

如上代码所示,我们把类X的private数据成员都已到了XImpl中,而在头文件中只保存了指向此结构体的一个指针,从而使我们可以不再包含#include <list>#include <C>#include <D> 只需要前置声明就可以了。另外注意到类X是private继承自类B的,一般情况下private继承应该使用containment来实现,所以也应该把B移到XImpl中,这样只需要前置声明class B 就可以了。

从上面的代码可以看到,Impl-idiom 实际上是一种把实现和接口分离的一种方法,让调用X的外部类尽量少的知道类X的细节,外部调用只需要知道X的public函数就可以了,其他的都可以不必知道。所以可以把private的成员变量和函数放置到Pimpl 中。但同时也要注意,并不是所有private的成员变量和函数都可以放置到Pimpl 中的。一般private的成员变量可以移到里面去,但是virtual类型的private函数是不可以的。另外protected的成员和函数也是不可以的。

下面讨论一下上面这种方式的占用空间和效率问题。很明显,这种方式会让类X多一个指针变量,所以会增大类的占用空间,具体增大多少要考虑到具体的类和内存对齐问题。从效率上讲,主要有两方面的问题,一是类X在构造的过程中要动态的在堆上申请一块内存供Pimpl 使用,这相比于直接在类中声明成员变量,效率上会低一些。另外在访问具体的变量和函数时,也会多了一步指针的操作(dereference)。

当然,针对以上提出的效率问题,书中先是给出了一个错误的解决方案,如下:

class Y
{
    /*...*/
    static const size_t sizeofx = /*some value*/;
    char x_[sizeofx];
}

//file y.cpp
#include "x.h"
Y::Y()
{
    assert(sizeofx >= sizof(X));
    new (&x_[0]) X;
}

Y::~Y()
{
    (reinterpret_cast<X*>(&x_[0]))->~X();
}

为什么上面的实现方法是错误的呢?原因如下:
首先看下面一段代码:

char* buf1 = (char*)malloc(sizeof(Y));
char* buf2 = new char[sizeof(Y)];
char buf3[sizeof(Y)];
new (buf1) Y; // ok, buf1 allocated dynamically
new (buf2) Y; // ok, buf2 allocated dynamically
new (&buf3[0]) Y; // error, buf3 may not be suitably                          aligned
(reinterpret_cast<Y*>(buf1))->~Y(); // ok
(reinterpret_cast<Y*>(buf2))->~Y(); // ok
(reinterpret_cast<Y*>(&buf3[0]))->~Y(); // error

从以上代码可以看出,以上代码用类的成员变量预留空间的方式,虽然可以免去动态申请内存的开销,但会有内存对齐的问题,所以是错误的。

那么除了上面错误的方法,正确的做法是什么呢,书中给出的答案如下:
第一种方法;

struct X::XImpl
{
    static void* operator new(size_t) {/*...*/}
    static void operator delete(void*) {/*...*/}
};
X::x() : pimpl_(new XImpl){}
X::~X() { delete pimpl_; pimpl_ = 0; }

以上方法利用的是重载new和operator运算符,然后利用分配固定内存块的方式,对分配XImpl进行优化。

第二种方法:
这是比第一种方法更通用的方法.

//用于分配固定内存块的优化类
//比如可以先申请一块内存池,然后在此内存池上再分配内存
class FixedAllocator
{
publicstatic FixedAllocator& Instance();
    void * Allocate(size_t);
    void Deallocate(void*);
}

struct FastArenaObject
{
    static void* operator new(size_t s)
    {
        return FixedAllocator::Instance()->Allocate(s);
    }
    static void operator delete(void* p)
    {
        FixedAllocator::Instance()->Deallocate(p);
    }
};

//我们只需要让XImpl继承自上面的类就可以了
struct X::XImpl : FastArenaObject
{
    /*...private stuff here...*/
};

说了这么多,其实一般情况下,我们并不需要实现最后面这一种快速分配的方式。在效率不是那么紧急的情况下,我们只需要用通常的”Pimpl-idiom”就可以了,并不需要实现后面这种快速的方式。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值