在dll接口中使用stl-classes时消除C4251警告的一种方法

时间:2013-05-07 12:40:10

标签: c++ visual-studio-2010

Common practice in dealing with warning c4251: class … needs to have dll-interface所述,在dll接口中使用stl-classes不是一个好习惯。给出了一个例子:

#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 个答案:

答案 0 :(得分:24)

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

  1. 您仍然可以为您的字段保留STL /模板类,并将它们用作参数类型,只要您将所有字段保密(无论如何这是一个很好的做法)。 Here是对此的讨论。要删除警告,您只需根据#pragma语句使用。

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

  3. 您可以使用PIMPL惯用法在私有实现类中隐藏您的STL字段,这些字段甚至不会从您的库中导出,也不会在其外部显示。

  4. 或者您可以按照here所述的方式实际导出所有必需的模板类专精,如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
    ...
    

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

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

    int *p_abc;
    int abc_len;
    

    类似于class MyFancyDataArray和字段

    char *p_str;
    int str_len;
    

    class MyFancyString之类的东西中,那么这将是一个更体面的解决方案(类似于第二点所描述的解决方案)。但这可能不是经常重新发明轮子的最佳习惯。

答案 1 :(得分:7)

或做最简单的事情,将__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;
};

答案 2 :(得分:6)

我不确定你想在这里解决哪个问题。有两个不同的问题:交叉编译器二进制兼容性和避免&#34;未定义的符号&#34;链接器错误。你引用的C4251警告谈到了第二个问题。这主要是一个非问题,特别是对于std::stringstd::vector这样的SCL类。由于它们已在CRT中实施,只要您的应用程序的双方使用相同的CRT,一切都将“正常工作”。在这种情况下,解决方案是禁用警告。

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

答案 3 :(得分:3)

只需使用Pointer To Implementation (pImpl)成语。