24

在 dll 接口中使用 stl 类作为处理警告 c4251 的常见做法不是一个好习惯:类 ... 需要解释 dll 接口。给出一个例子:

#include <iostream>
#include <string>
#include <vector>


class __declspec(dllexport) HelloWorld
{
public:
    HelloWorld()
    {
        abc.resize(5);
        for(int i=0; i<5; i++)
            abc[i] = i*10;

        str="hello the world";
    }
    ~HelloWorld()
    {

    }

    std::vector<int> abc;
    std::string str;

};

编译此文件时,可以观察到以下警告:

 warning C4251: 'HelloWorld::str' : class 'std::basic_string<_Elem,_Traits,_Ax>' needs to have dll-interface to be used by clients of class 'HelloWorld'    
 warning C4251: 'HelloWorld::abc' : class 'std::vector<_Ty>' needs to have dll-interface to be used by clients of class 'HelloWorld'

那么问题是我们如何在不使用 STL 类向量和字符串的情况下实现相同的功能。我能想到的一种实现如下:

class __declspec(dllexport) HelloWorld2
{
public:
    HelloWorld2()
    {
         abc_len = 5;
         p_abc = new int [abc_len];
         for(int i=0; i<abc_len; i++)
             p_abc[i] = i*10;

         std::string temp_str("hello_the_world");
         str_len = temp_str.size();
         p_str = new char[str_len+1];
         strcpy(p_str,temp_str.c_str());
    }
    ~HelloWorld2()
    {
        delete []p_abc;
        delete []p_str;
    }

    int *p_abc;
    int abc_len;
    char *p_str;
    int str_len;



};

如您所见,在新实现中,我们使用 int *p_abc 替换向量 abc 和 char *p_str 替换字符串 str。我的问题是是否有其他优雅的实现方法可以做到这一点。谢谢!

4

4 回答 4

32

我认为您至少有 5 种可能的选择来解决这个问题:

  1. 您仍然可以为您的字段保留 STL/模板类并将它们用作参数类型,只要您将所有字段保持私有(无论如何这是一个好习惯)。是关于此的讨论。要删除警告,您可以简单地使用相应的#pragma语句。

  2. 您可以创建带有私有 STL 字段的小型包装类,并将包装类类型的字段公开给公众,但这很可能只会将警告移到其他地方。

  3. 您可以使用PIMPL习惯用法将您的 STL 字段隐藏在私有实现类中,它甚至不会从您的库中导出,也不会在它之外可见。

  4. 或者,您可以按照 C4251 警告中的建议,以此处描述的方式实际导出所有必需的模板类特化。简而言之,您必须在HelloWorld课程之前插入以下代码行:

    ...
    #include <vector>
    
    template class __declspec(dllexport) std::allocator<int>;
    template class __declspec(dllexport) std::vector<int>;
    template class __declspec(dllexport) std::string;
    
    class __declspec(dllexport) HelloWorld
    ...
    

    顺便说一句,这些导出的顺序似乎很重要:向量类模板在内部使用分配器类模板,因此分配器实例化必须在向量实例化之前导出。

  5. 直接使用内在函数可能是你最糟糕的选择,但如果你封装你的字段

    int *p_abc;
    int abc_len;
    

    在类似class MyFancyDataArray的领域

    char *p_str;
    int str_len;
    

    在类似的class MyFancyString情况下,这将是一个更体面的解决方案(类似于第二点描述的解决方案)。但过于频繁地重新发明轮子可能不是最好的习惯。

于 2014-02-26T22:22:56.587 回答
8

或者做最简单的事情,将 移至__declspec您要导出的唯一成员:

class HelloWorld
{
public:
    __declspec(dllexport) HelloWorld()
    {
        abc.resize(5);
        for(int i=0; i<5; i++)
            abc[i] = i*10;

        str="hello the world";
    }
    __declspec(dllexport) ~HelloWorld()
    {
    }
    std::vector<int> abc;
    std::string str;
};
于 2016-02-08T17:39:52.737 回答
8

我不确定你想在这里解决哪个问题。有两个不同的问题:交叉编译器二进制兼容性和避免“未定义符号”链接器错误。您引用的 C4251 警告谈到了第二个问题。这实际上基本上不是问题,尤其是对于像std::string和之类的 SCL 类std::vector。由于它们是在 CRT 中实现的,只要您的应用程序的双方都使用相同的 CRT,一切都会“正常工作”。这种情况下的解决方案是禁用警告。

交叉编译器二进制兼容性OTOH,这是您链接的另一个stackoverflow问题中讨论的内容,是一个完全不同的野兽。为了解决这个问题,您基本上必须保持所有公共头文件不提及任何 SCL 类。这意味着您要么必须 PIMPL 一切,要么在任何地方使用抽象类(或混合使用)。

于 2016-02-09T07:36:08.853 回答
3

只需使用指向实现 (pImpl) 的成语。

于 2014-02-26T22:29:14.440 回答