수안이의 컴퓨터 연구실

  • Mainpage
  • About Me
  • Tags
  • Metapage
  • Notice
  • Location
  • Keywords
  • Guestbook
  • Admin
  • Write an Article
  • Total | 1694418
  • Today | 163
  • Yesterday | 606

1 Articles, Search for 'Bridging'

  1. 2007/07/27 C++ in theory: Bridging Your Classes with PIMPLs
Programming/C++2007/07/27 17:10

C++ in theory: Bridging Your Classes with PIMPLs

출처 : http://www.devarticles.com/c/a/cplusplu ··· impls%2F

C++ in theory: Bridging Your Classes with PIMPLs
(Page 1 of 4 )

Very often, when a program takes longer to compile after you have made what appear to be trivial changes, the blame can be laid at the door of dependency chains between header files. One change can trigger the need for a massive rebuild. J. Nakamura explains a way to make header files insensitive to any change -- thus saving all that rebuild time -- by using pimpl.


Looking For Problems

Large development projects can become a drain... not just on your brain, but on your valuable time as well. When you notice increasing compilation time after making what you thought to be trivial changes, you can claim to have found a problem (some might say that you are looking for one). The application might run fine and be without bugs, but those increasing build times do not really speed up the development process. They are also very annoying when you are making small changes in a debug cycle.

Most often the cause for these extreme build times can be found in large dependency chains between your header files. Consider the following header files:

// MyBase.h
class MyBase {
public:
  int foo();
};

// MyDerived.h
#include “MyBase.h”
class MyDerived : public MyBase {
public:
  int bar();
};

If you make a change to MyBase.h (let's assume you need a new private or protected member variable), MyDerived and everything, including its header file, will have to be recompiled as well. You can imagine that in a large project, many header files will be dependent on each other, often many levels deep. At a certain point you will notice that you are becoming reluctant to make changes to a class, simply because these changes will trigger a massive rebuild (and might evoke some unpleasant primitive reactions in your colleagues).

There are tools available to analyze these dependencies. Personally I prefer to use Doxygen, which is used to generate documentation from sources. Doxygen can use ‘dot’ from GraphViz to generate nice pictures of header file dependencies. Best of all, it is free!

C++ in theory Bridging Your Clsasses with PIMPLs

There are ways to minimize the dependency problem -- for example, by forbidding coders to include header files in header files! But even so, your changes to MyBase.h will trigger a rebuild of MyDerived.cpp, since that is the file that includes the MyBase header now.

Wouldn't it be nice to be able to make the header file insensitive to any change? We would like to prevent recompilation when making changes to a private interface. This is where we use pimpl for [Sutter].

C++ in theory: Bridging Your Classes with PIMPLs - The Private Implementation
(Page 2 of 4 )

The private implementation is a variation of the Bridge Pattern [Gamma], which is intended to decouple an abstraction from its implementation, so that the two can vary independently.

When you take a look at the C++ header and class declaration, you will notice that both the public and private interface is usually declared at the same location. Conceptually this is quite strange: in effect, the class lets you peek at its private parts, which should be of no concern to you. So let's hide it!

// MyClass.h
class MyClassImpl;    // forward declaration
class MyClass {
public:
  MyClass();
~MyClass();
  int foo();
private:
  MyClassImpl *m_pImpl;
};

The only visible private member variable in our class is the pimpl. We defined a forward declaration of the private implementation class; we don’t need any further information about it. The compiler needs to know how much memory to reserve for MyClass. Since we are only storing a pointer (which on my win32 platform is 4 bytes big), the compiler doesn’t care what the memory layout of MyClassImpl looks like.

Unless you make changes to the public interface, this header file is unlikely to change! Lets take a look at the implementation.

// MyClass.cpp
class MyClassImpl {
public:
int foo() {
var*=var;
return bar();
}
int bar() { return var & 0xFF; }
int var;
};

MyClass::MyClass()
  : m_pImpl(new MyClassImpl)
{}

MyClass::~MyClass()
{
try { delete m_pImpl; }
catch (...) {}
}

int MyClass::foo()
{ return m_pImpl->foo(); }

The implementation class is defined in our source file, literally confined to the source implementation of our class. Any changes to our implementation class (the introduction of new member variables and member functions, for example) will trigger no recompilations outside this source file.

Basically all public functions redirect the call to the function in the private implementation when you implement a pimpl, although this is not strictly necessary. MyClass::foo() could also be implemented as:

int MyClass::foo() {
  m_pImpl->var*=m_pImpl->var;
  return m_pImpl->bar();
}

I consider it to be more effective and cleaner to just redirect the call to the pimpl. It also saves you multiple pointer dereferences.

The destruction of the pimpl is wrapped in a try-catch block, because we don’t want any exceptions to escape our own destructor. We do this because, when MyClass is destroyed by the exception-handling mechanism during the stack-unwinding part of exception propagation, any exceptions triggered during this destruction will force C++ to call the terminate function [Meyers]. Immediate termination of your application is one of the worst things that can happen to it!

C++ in theory: Bridging Your Classes with PIMPLs - Pimpl Drawbacks
(Page 3 of 4 )

You do pay a price for pimpls when it comes to performance. Let's set up some code:

#include <stdio.h>
#include <time.h>

class MyClassA {
public:
  MyClassA( ) : c(0) {}
  char foo( ) { return c; }
  char c;
};

class MyClassB {
public:
  MyClassB( ) : m_pImpl(new MyClassA) { }
  ~MyClass( ) { try { delete m_pImpl; } catch ( ... ) { } }
  char foo( ) { return m_pImpl->foo( ); }
private:
  char c;
  MyClassA *m_pImpl;
};


Here we have set up MyClassA to be the private implementation of MyClassB. We are going to measure how much performance overhead the function indirection and memory allocation require.

int main(int argc, char *argv[])
{
clock_t start, finish; // stores time values
for (int count = 0; count < 10; ++count)
{
  /* our test code will go here */
}
return 0;
}

Each construction/deconstruction needs to allocate/free memory.

Since the implementation is hidden in a separate class behind a pointer, every time our pimpled class is created, we need to allocate memory on the heap. And every time our pimpled class is destroyed, we need to free memory. Compared to common operations like function calls, memory allocation and deallocation are relative expensive operations.

Normally you would not notice or worry about this performance cost, but when you have to construct/destruct an array of your pimpled object repeatedly, you will notice a difference:

  start = clock();
  for (int i = 0; i < 10; ++i )  MyClassA arryA[0xffff];
  finish = clock();
  int durationA = finish – start;
  (void)printf(“ticks spent on MyClassA: %d\n”, durationA);

  start = clock();
  for (int j = 0; j < 10; ++j )  MyClassB arryB[0xffff];
  finish = clock();
  int durationB = finish – start;
  (void)printf(“ticks spent on MyClassB: %d\n”, durationB);

clock() calculates the processor time used by the calling process; we can use this to measure how much time is spent on constructing and destructing MyClassA arryA[0xffff] and MyClassB arryB[0xffff] 10 times. I prefer to cast the result of printf to void (it returns the number of characters printed or a negative value if an error occurs), just to make it clear that we are ignoring the return value.

When you run the sample, you will notice that the construction of the pimpled class takes a lot more time than the non-pimpled one. On my PIV 3Ghz the result was:

ticks spent on MyClassA: 31
ticks spent on MyClassB: 719

Though the sample might be a bit extreme, you can see that the difference is not trivial. Remember that the construction of an n-array of objects, constructs n objects on the stack for you. In this case it means that the MyClassB constructor is called n times, performing n allocations on the heap!

To make things worse, this example doesn’t do anything with the array, and since it immediately goes out of scope, the destructor is called n times as well.

Access to hidden members comes at the cost of at least one extra indirection.

Each access of a member in the pimpled class can require at least one extra indirection. A pointer dereference can be quite an extra cost if the code is called often:

  MyClassA aObj;
  start = clock();
  for (int k = 0; k < 0xffffff; ++k)  (void)aObj.foo();
  finish = clock();
  durationA = finish – start;
  (void)printf(“ticks spent calling MyClassA::foo(): %d\n”, durationA);

  MyClassB bObj;
  start = clock();
  for (int l = 0; l < 0xffffff; ++l)  (void) bObj.foo();
  finish = clock();
  durationB = finish – start;
  (void)printf(“ticks spent calling MyClassB::foo(): %d\n”, durationB);

Running another extreme sample, my result was:

ticks spent calling MyClassA::foo(): 1500
ticks spent calling MyClassB::foo(): 3156

Again, this is not a trivial difference when the code is executed often!

Access to public members comes at the cost of an extra indirection.

Sometimes the private implementation needs to access public member functions of the pimpled class. Storing a “back pointer” to the visible object can easily do this. By convention, this back pointer is usually named “self.”

// MyClass.h
class MyClass {
public:
  MyClass();
  ~MyClass();
  void foo();
void bar();
private:
  MyClassA *m_pImpl;
};

// MyClass.cpp
class MyClassImpl {
public:
  MyClassImpl(MyClass *_self) : self(_self) {}
  void foo() { self->bar(); }
};
MyClass::MyClass()
  : m_pImpl(new MyClassImpl(this))
{}
MyClass::~MyClass()
{
try { delete m_pImpl; }
catch (...) {}
}

Looking at the previous example, it is clear that an extra indirection back will drive the performance cost even further up.

There is a small space overhead.

“Space overhead?” you might ask -- and you might not think this is a big deal. When you have a lot of small objects which you have pimpled, however, that extra pointer does start to count (when you need a back pointer, you will actually need two extra pointers). Try the following on the test code above:

  (void)printf(“sizeof MyClassA: %d\n”, sizeof(MyClassA));
  (void)printf(“sizeof MyClassB: %d\n”, sizeof(MyClassB));

Result will vary among compilers but the overhead I have is actually 7 bytes:

sizeof MyClassA: 1
sizeof MyClassB: 8

Even though a pointer only costs 4 bytes on my machine, the compiler aligns it on a 4-byte boundary, wasting 3 bytes (char c takes one byte). The overhead will be even larger on an AS/400 or 64-bit machine. Of course you can configure this in your build settings...but that is not my point.

C++ in theory: Bridging Your Classes with PIMPLs - Summary
(Page 4 of 4 )

The pimpl allows you to decouple the interface of your class from its implementation. On top of that, the implementation is not permanently bound to the interface. Any compile time dependencies are completely eliminated with the pimpl. This frees you from the fear to make changes to the implementation.

So use the pimpl when you want to hide the implementation details of your class. Before you do, however, make certain that you understand the performance penalties involved.

References

[Sutter] Exceptional C++ item 26~30.

[Gamma et all] Design Patterns – Bridge.

[Meyers] More Effective C++ item 11


"C++" 카테고리의 다른 글
  • DLL Conventions: Issues and Solutions (0)2007/07/27
  • More on Handling Basic Data Types (0)2007/07/27
  • C++ in theory: Bridging Your Classes with PIMPLs (0)2007/07/27
  • C++ In Theory: The Singleton Pattern (0)2007/07/27
  • <iostream> vs. <iostream.h> (0)2007/07/03
2007/07/27 17:10 2007/07/27 17:10
Posted by webdizen
Tags Bridging, C++Keyword C++, Classes, PIMPLs
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3102

Leave your greetings.

[로그인][오픈아이디란?]

«Prev  1  Next»

RSS HanRSS
Blog Image
webdizen
이곳은 컴퓨터에 대해 연구하고, 공유하고, 소통하기 위한 연구실입니다. 개인적으로는 OLAP, Data Mining, Semantic Web, Data Modeling에 대해서 연구하고 있습니다.

Categories

전체 (3009)
Webdizen (141)
Life (6)
Diary (16)
Blog (9)
IDEA (2)
Travel (10)
Book (16)
Photo (7)
Movie (8)
Music (14)
Leisure Sports (10)
Funny (6)
Hardware (121)
Software (120)
Windows (5)
Unix & Linux (120)
Installation (5)
Kernel (10)
System (34)
Develop (22)
X-Window (0)
Applicaton (31)
Security (4)
Framework (2)
Hadoop (2)
Programming (804)
Algorithm & Data Structure (1)
Assembly (38)
UNIX/Linux C (95)
C++ (128)
STL (4)
Java (38)
Win32 API (92)
ATL/COM (44)
MFC (151)
.NET (26)
WCF/WPF (4)
C# (28)
Network Programming (17)
Database Programming (12)
OpenGL / DirectX (13)
Multimedia Programming (0)
Game Programming (21)
Parallel Distributed Progra... (0)
Reverse Engineering (0)
Debugging (9)
Python (1)
Ruby (1)
Ruby on Rails (1)
QT (4)
GTK (0)
JSP (0)
PHP (6)
ASP.NET (6)
ASP (2)
Development (28)
Useful Library (2)
Data Modeling (0)
Database (105)
Oracle (4)
MSSQL (41)
MySQL (2)
Data Warehouse (2)
Data Mining (4)
Network (66)
Web (79)
DHTML (4)
XHTML (1)
Javascript (1)
CSS (1)
AJAX (9)
XML (11)
Flex (1)
Silverlight (3)
Security (91)
DoS (1)
Kernel (10)
Scanning (3)
Sniffing (0)
Spoofing (4)
Overflow (28)
Web (11)
Shell (10)
Format String (14)
Window (2)
Embedded (70)
Multimedia (27)
Mobile (14)
Graphic (24)
Management (633)
Knowledge (581)
Hadoop (0)

Notice

  • 메타 블로그 사이트에 등록
  • 새해 맞이 블로그의 변화
  • 블로그 명칭 변경
  • 도메인(www.webdizen.net) 구...
  • TEXTCUBE 1.6.1로 업그레이드...

Tags

  • GetProcAddress
  • 딤플
  • 컴포넌트
  • Desktop
  • 삼성
  • 보안 솔루션
  • 빌 게이츠
  • Next Statement
  • Functor
  • 암기력
  • 컨트롤 삽입
  • 자연과학대학
  • UTMP
  • ShellExcute
  • 마주앙 샤도네이
  • 공유 스캔
  • 제이콥스 그레나쉬
  • 서술 작업
  • 강대라이크
  • 백록관

Recent Articles

  • 트위터(Twitter)의 시작!.
  • 청년 리더의 조건.
  • 애플의 타블렛 PC - 아이패드....
  • 미래의 인터페이스 - 육감 기....
  • 기초발성법 동영상 강좌.

Recent Comments

  • 학교 과제물중 쓰레드에 대하....
    장진혁 03/17
  • 관리자만 볼 수 있는 댓글입....
    비밀방문자 03/12
  • 상대방의 이야기를 열심히 경....
    DoNuts 03/03
  • Lots of students know techn....
    Bobbi35Shannon 02/25
  • 좋은글 잘 보고 갑니다..
    Und_hacker 01/08

Recent Trackbacks

  • printf,scanf를 이용한 형식....
    yundream의 프로그래밍 이야기 03/10
  • 파일 열기/저장하기 CFileDialog.
    은마군의 나태블록 2009
  • World IT Show 2008.
    상우 :: Oranzie's BLOG 2008
  • cvs서버 설치하기.
    3인3색 2008
  • 속속 공개되는 Google Chart....
    PHP와 Web 2.0 2007

Archive

  • 2010/02 (1)
  • 2010/01 (6)
  • 2009/12 (5)
  • 2009/09 (3)
  • 2009/08 (1)

Calendar

«   2010/03   »
일 월 화 수 목 금 토
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      

Bookmarks

    • Administration
      • IIS.NET
      • NTFAQ
      • OS의 모든 것
      • 리눅스포털
    • Database
      • SQL Server Central
      • SQL Team
    • Development
      • .NET Heaven
      • ASP Alliance
      • ASP.NET 2.0
      • Bullog.net
      • C# Corner
      • C++ (C PlusPlus.com)
      • C++ Reference
      • CodeGuru
      • CodePlex
      • DebugLab
      • Dev Articles
      • Devpia
      • DotNet Junkies
      • DotNet Zone
      • Driver Online
      • GOSU.NET
      • HOONS 닷넷
      • Joinc 팀블로그
      • KOSR
      • MSDN Home Page
      • OSR Online
      • Sky.ph - 개발자 커뮤니...
      • TAEYO.NET
      • The Code Project
      • WindowsClient.net
      • 김상욱의 개발자 Side
      • 조인시 위키
    • Human Networks
      • belief21c's e-space
      • I think I can
      • Invisible Rover's Blog :D
      • Rodman®
      • ■ Feel So Good~! ■
      • 까만 나비
      • 나를 가꾸는 시간.
      • 나만의 즐거움~~!
      • 단녕
      • 상우 :: Oranzie's BLOG
    • Information Technology
      • Microsoft TechNet
      • 지디넷코리아 - 글로벌...
    • Security
      • FoundStone
      • milw0rm
      • NewOrder
      • OpenRCE
      • Phrack.org
      • Reverse Engineering b1...
      • Reverse Engineering Team
      • RootKit
      • SecurityFocus
      • SecurityXploded by Nag...
      • Wow Hacker
      • Zone-H
Textcube
Louice Studio Inc.
Powered by Textcube. Original designed by Tistory.