2014-09-06 54 views
0

我有一個包含類定義的頭文件。該類包含一些公共函數和一些私有變量。該類被編譯成可執行文件。針對修改後的頭文件編譯

可以說有人拿這個頭文件並創建一個「公共」副本。他刪除所有包含和私有變量(對未定義的符號使用前向聲明)。然後,他編寫自己的代碼(調用相關類的公共函數),對「公共」頭文件進行編譯並創建.so文件。

這會圖書館工作正常

  • 如果它與可執行文件鏈接?
  • 如果它是在運行時動態加載的?
+1

您描述了一個ODR違規。這樣的程序表現出未定義的行爲;如果它看起來工作,那麼只有意外。相反,讓有問題的類成爲一個接口:一個沒有數據成員的類,並且所有的方法都是純虛擬的。從庫中公開一個工廠函數(當然,它會返回一個指向某個從接口派生的類並實現其方法的指針)。這樣,沒有實現細節需要泄漏到公共頭部。 – 2014-09-06 04:33:05

+0

不幸的是,我的一位客戶正在爭辯說這會起作用。他希望將他的模塊作爲插件進行開發,並且他不想將其綁定到代碼的其餘部分。否則我希望有一些彈藥能夠說服他。 – 2014-09-06 04:44:06

+0

只要他的代碼只使用由代碼創建的實例的指針或引用,他就能夠脫身。但是,如果他的代碼嘗試自行創建該類的實例,則會出現問題(因爲如果他更改/刪除了任何成員變量,將無法提供正確的內存量)。此外,無論您何時對班級進行任何更改,他都必須重新複製頭文件並重新進行修改。即使他的想法有效,它也是一種脆弱的做事方式。 – TheUndeadFish 2014-09-06 06:22:50

回答

0

正如評論中所述,您所描述的內容不起作用,但目標是合理的。我的理解是,您希望隱藏類的實現細節,同時爲插件提供固定的接口,以便插件代碼開發可以與程序的其餘部分分離。

你不能僅僅通過給出一個錯誤的標題從字面上隱藏私有成員數據和函數。首先,Igor Tandetnik指出ODR違規。這不只是一個任意的規則。私有數據會影響存儲對象所需的內存,從而影響代碼如何處理該對象。公共函數和私有函數的相對地址必須在多態性的常見vtable實現中已知。

我們需要間接。我們的接口將告訴客戶端代碼它的公共函數是什麼,並且它只需要空間來存儲指向實現類的指針。實現類的細節不需要知道。這是pimpl idiom。以下概述了它如何與動態加載一起使用。

的main.cpp

#include <iostream> 
#include <dlfcn.h> 

#include "interface.h" 

typedef int (bar_type)(const Interface&); 

int main() { 
#ifndef EXE_INPUT 
#define EXE_INPUT 5 
    Interface interface(EXE_INPUT); 
#endif 

    void* plugin = dlopen("plugin.so", RTLD_LAZY); 
    if (plugin == NULL) { 
     std::cout << dlerror() << std::endl; 
     return 1; 
    } 

    bar_type* bar_ptr = (bar_type*)dlsym(plugin, "bar"); 
    if (bar_ptr == NULL) { 
     std::cout << dlerror() << std::endl; 
     return 1; 
    } 

    const int ret = (*bar_ptr)(interface); 

    std::cout << "The value from the plugin is " << ret << std::endl; 
} 

interface.h

#ifndef INTERFACE_H 
#define INTERFACE_H 
class Implementation; 

class Interface 
{ 
public: 
    Interface(const int); 
    ~Interface(); 
    int foo(int) const; 
private: 
    Implementation* imp_ptr; 
}; 
#endif 

interface.cpp

#include "interface.h" 

struct Implementation { 
    Implementation(const int v) 
     : v(v) 
    {} 

    int foo(const int w) { 
     return v * w; 
    } 

    int v; 
    /* this struct is not exposed, do whatever you want here */ 
}; 

Interface::Interface(const int v) 
    : imp_ptr(new Implementation(v)) 
{} 

Interface::~Interface() { 
    delete imp_ptr; 
} 

/* if this signature changes or other functions get added 
* to Interface, plugin must be recompiled */ 
int Interface::foo(const int w) const { 
    return imp_ptr->foo(w); 
} 

plugin.cpp

#include "interface.h" 
#include "plugin.h" 

extern "C" int bar(const Interface& i) 
{ 
#ifndef PLUGIN_INPUT 
#define PLUGIN_INPUT 11 
    return i.foo(PLUGIN_INPUT); 
#endif 
} 

plugin.h

#ifndef PLUGIN_H 
#define PLUGIN_H 
#include "interface.h" 
extern "C" int bar(const Interface& i); 
#endif 

編譯和鏈接。我恰好在運行OS X.對於Linux,刪除「-undefined dynamic_lookup」。

g++-4.8 -o main main.cpp interface.cpp 
g++-4.8 -shared -fpic -undefined dynamic_lookup -ldl -o plugin.so plugin.cpp 

請注意,我們分別編譯和鏈接。具體來說,plugin.cpp不知道interface.cpp中的內容。

$ ./main 
The value from the plugin is 55 

您可以隨意更改interface.cpp而無需重新編譯插件。這裏的動態加載不是絕對必要的。它也可以用於動態鏈接。注意:C++標準對用戶定義的類如何在內存中進行佈局提出了很少的要求。如果你試圖在插件和主程序之間傳遞用戶類實例,你可能得不到你所期望的,特別是如果主程序和插件是用不同的編譯器編譯的話。