Today, when i was reading the code of “scoped_ptr_impl” in chromium, the code below confused me:

template <class T, class D>
class scoped_ptr_impl {
 public:
  explicit scoped_ptr_impl(T* p) : data_(p) { }

  struct Data : public D {
    explicit Data(T* ptr_in) : ptr(ptr_in) {}
    Data(T* ptr_in, const D& other) : D(other), ptr(ptr_in) {}
    T* ptr;
  };

  Data data_;

Why the pointer is packaged inside the scoped_ptr_impl instead of using D deleter directly. The anwser is in Here, and the article described it in details. I make a short summary for it:

1,Empty class is not empty

class Test{}

There is no member in class Test. But the instance of Test will occupy 1 byte in memory which mean sizeof(Test) == 1. We know that the instance must be addressed in c++, so the “1 byte occupied” is used for addressing the instance.

2,Don’t look down upon the occupied of empty class

template <class T, class Alloc = allocator<T> >
    class list {
      . . .
      Alloc alloc_; 
      struct Node { . . . };
      Node* head_;      

     public:
      explicit list(Alloc const& a = Alloc())
        : alloc_(a) { . . . }
      . . .
    };

Think about the “std::list” implementation. There is a Alloc member in std::list and the class of Alloc is empty. So it will make a few extra bytes occupied in the list object by default, and it doesn’t seem so bad until you imagine a big vector of these lists, as in a hash table.

3,The best way to eliminating bloat

template <class Base, class Member>
    struct BaseOpt : Base {
      Member m;
      BaseOpt(Base const& b, Member const& mem) 
        : Base(b), m(mem) { }
    };

template <class T, class Alloc = allocator<T> >
    class list {
      . . .
      struct Node { . . . };
      BaseOpt<Alloc,Node*> head_;
      
     public:
      explicit list(Alloc const& a = Alloc())
        : head_(a,0) { . . . }
      . . .
    };

By the example code above, the majar concept of optimizing is:

Thought sizeof(Alloc) == 1, it is sizeof(BaseOpt) == sizeof(Member) because most of Standard-conforming compilers will optimize the derived class's size while the base class is empty.

So after packaging the member in std::list, there will be sizeof(head_) == sizeof(Node*), it eliminate the memory occupied by empty class.

It is the reason why scoped_ptr_impl using packaged pointer. The deleter(D) is a empty-class by default, it is the same reason to do so like std::list.

According to the article, it is said that:

A Watcom engineer reports that STL benchmarks ran 30% faster after they implemented the empty-base optimization.

Amazing, isn’t it, thanks google!



评论需要翻墙 for disqus