Hiding Implementation Details in C++
Get the implemention details out of the C++ include file
Introduction
I want to share a simple way of hiding implementation details in C++.
The Classic Way
A C++ class definition lets you control access to private
data and methods while exposing a public
interface to manipulate the data.
// Define a public interface in foo.h
class Foo
{
public:
Foo() {}
void SetPosition(float x, float y);
private:
float m_x, m_y; // private data
void PrivateMethod() {} // private method
};
// Define a private implementation in foo.cpp
void Foo::SetPosition(float x, float y)
{
m_x = x; m_y = y;
}
Problems...
While the code for the implementation is hidden in the CPP file, the encapsulation is incomplete because:
private
data members and methods are defined in the header file- changing the implementation often requires changing the header file because
- you add or change data members or
- you add or change
private
methods
Ideally, the header file should be something you read to learn about the public
interface for a class and nothing else.
Improved
To solve this problem, you can use inheritance.
// Define a public interface in foo.h
class Foo
{
public:
virtual void SetPosition(float x, float y) = 0;
static Foo* Create();
protected:
Foo() {} // hide
};
// Define a private implementation in foo.cpp
class FooImpl : public Foo
{
public:
FooImpl() {}
void SetPosition(float x, float y)
{
m_x = x; m_y = y;
}
private:
float m_x, m_y; // private data
void PrivateMethod() {} // private method
};
// Define factory method
Foo* Foo::Create()
{
return new FooImpl();
}
The client no longer sees any of the implementation details. The cost is that we are now required to have a factory method and virtual methods.
Improved With No Virtual Methods
Adding virtual methods might not be acceptable if your class objects are streamed off disk (memory ready) or you don't want to incur the performance penalty. Here is a solution that works without virtual methods:
// Define a public interface in foo.h
class Foo
{
public:
void SetPosition(float x, float y);
static Foo* Create();
protected:
Foo() {} // hide
};
// Define a private implementation in foo.cpp
class FooImpl : public Foo
{
public:
FooImpl() {}
// allow Foo methods to see our private members
float m_x, m_y; // private data
void PrivateMethod() {} // private method
};
// Define factory method
Foo* Foo::Create()
{
return new FooImpl();
}
// downcasting methods
inline FooImpl * GetImpl(Foo* ptr) { return (FooImpl *)ptr; }
inline const FooImpl * GetImpl(const Foo* ptr) { return (const FooImpl *)ptr; }
// Define public method
void Foo::SetPosition(float x, float y)
{
FooImpl * f = GetImpl(this);
f->m_x = x; f->m_y = y;
}
By downcasting in the class methods, we get access to the implementation data without virtual methods.
Conclusion
This technique should probably not be used for small classes such as vectors, where hiding data members is not as important. In this situation, inline methods need access to the data in the header file. But any class that has a non-trivial data structure that might change would benefit from this way of insulating the header file from changes in the implementation.
History
- Sep-2009: Article submitted