2014-09-06 50 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++标准对用户定义的类如何在内存中进行布局提出了很少的要求。如果你试图在插件和主程序之间传递用户类实例,你可能得不到你所期望的,特别是如果主程序和插件是用不同的编译器编译的话。