在編寫C++動態庫的過程中,我們常常會聽到某個要求:請隱藏動態庫頭文件裏類接口裏的成員變量!或者自己在編寫動態庫時,突然意識到自己好像讓調用者看到的信息太多了,而這些信息根本無需被調用者看到,往往調用者只需要接口函數而已,所以給他們接口函數就可以了。
暴露動態庫頭文件類接口裏的成員變量有很多壞處:
1、增加頭文件更新次數。如果成員變量不被隱藏,則每次修改成員變量都需要給調用者更新頭文件。
2、暴露給用戶太多信息。編寫庫的目的一個是方便,另一個就是私密性,讓類的實現部分在用戶端不可見,如果過多地暴露成員變量,則很容易造成信息泄露。
3、增加warning C4251。我們有時候會接到消除代碼中warning的任務,這在要求很高的源碼項目中很常見,而warning C4251便是很常見的一種警告,它警告的是在動態庫頭文件中接口類的成員函數中含有模板類,這在std標準庫頻繁使用的項目中很容易發生,如含有vector、string等成員變量,這時就需要隱藏這些成員變量,以消除此warning。
入正題,如何隱藏動態庫頭文件類接口裏的成員變量呢?使用C++ Implement。
C++ Implement其實很簡單,有兩種實現方式:
1、把接口類只當做一個殼子,實際的類實現,全部都放在另一個Impl類中。
2、把所有成員變量放在另一個Impl類中,Impl相當於成員變量的外殼。
以上兩種方式都必須遵守一個關鍵點:Impl類的定義和實現都絕不能出現在開放給用戶的動態庫頭文件裏。你可以把定義和實現都放在源文件裏,也可以定義放在不開放的頭文件裏,實現放在源文件裏。
代碼示例:
先看看不推薦的方式,即將成員變量暴露給用戶:
頭文件:
#ifndef MY_DLL__H_
#define MY_DLL_H_
#include <vector>
using std::vector;
#ifndef MY_DLL_EXPORT
#define DLL_EXPORT __declspec(dllimport)
#else
#define DLL_EXPORT __declspec(dllexport)
#endif
class DLL_EXPORT MyClass
{
public:
MyClass();
~MyClass();
public:
void DoSomething(); //給用戶調用的接口函數
private:
vector<int> vec_member_; //暴露給用戶的成員變量,用戶實際並不關心
};
#endif // MY_DLL_H_
源文件:
#include "stdafx.h"
#include "dll_class.h"
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
void MyClass::DoSomething()
{
vec_member_.push_back(1);
}
C++ Implement方式第一種:把接口類只當做一個殼子,實際的類實現,全部都放在另一個Impl類中:
頭文件:
#ifndef MY_DLL__H_
#define MY_DLL_H_
#include <vector>
using std::vector;
#ifndef MY_DLL_EXPORT
#define DLL_EXPORT __declspec(dllimport)
#else
#define DLL_EXPORT __declspec(dllexport)
#endif
class DLL_EXPORT MyClass
{
public:
MyClass();
~MyClass();
public:
void DoSomething(); //給用戶調用的接口函數
private:
void* impl_; //將所有實際功能實現放至類Impl中,定義一個void類型的類Impl指針,在構造函數中實例化
};
#endif // MY_DLL_H_
源文件:
#include "stdafx.h"
#include "dll_class.h"
class Impl
{
public:
Impl();
~Impl();
public:
void DoSomething(); //實際功能函數
private:
vector<int> vec_member_;
};
Impl::Impl()
{
}
Impl::~Impl()
{
}
void Impl::DoSomething()
{
vec_member_.push_back(1); //實際功能實現,以前的MyClass類中實現改爲在Impl類中實現
}
MyClass::MyClass()
:impl_(nullptr)
{
impl_ = new Impl(); //MyClass成員函數中實例化Impl類
}
MyClass::~MyClass()
{
if (impl_ != nullptr)
delete impl_;
impl_ = nullptr;
}
void MyClass::DoSomething()
{
Impl* impl = (Impl*)impl_;
impl->DoSomething(); //通過Impl類實例調用功能函數
}
C++ Implement方式第二種:把所有成員變量放在另一個Impl類中,Impl相當於成員變量的外殼。
頭文件:
#ifndef MY_DLL__H_
#define MY_DLL_H_
#include <vector>
using std::vector;
#ifndef MY_DLL_EXPORT
#define DLL_EXPORT __declspec(dllimport)
#else
#define DLL_EXPORT __declspec(dllexport)
#endif
class DLL_EXPORT MyClass
{
public:
MyClass();
~MyClass();
public:
void DoSomething(); //給用戶調用的接口函數
private:
void* impl_; //將成員變量移至類Impl中,定義一個void類型的類Impl指針,在構造函數中實例化
};
#endif // MY_DLL_H_
源文件:
#include "stdafx.h"
#include "dll_class.h"
class Impl
{
public:
Impl();
~Impl();
public:
vector<int> vec_member_; //將之前暴露給用戶的成員變量變爲Impl類的成員變量,再通過Impl類的實例調用此成員
};
Impl::Impl()
{
}
Impl::~Impl()
{
}
MyClass::MyClass()
:impl_(nullptr)
{
impl_ = new Impl(); //構造函數中實例化Impl類
}
MyClass::~MyClass()
{
if (impl_ != nullptr)
delete impl_;
impl_ = nullptr;
}
void MyClass::DoSomething()
{
Impl* impl = (Impl*)impl_;
impl->vec_member_.push_back(1); //通過impl_成員調用vec_member_
}