在dll接口中使用静态类时消除C4251警告的一种方法

One way of eliminating C4251 warning when using stl-classes in the dll-interface

本文关键字:警告 方法 一种 C4251 接口 dll 静态类      更新时间:2023-10-16

在dll-interface中使用still -classes不是一个好的做法,处理警告c4251: class…的常见做法需要有dll-interface解释。给出了一个例子:

#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类vector和string的情况下实现相同的功能。我能想到的一种实现如下:
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,我的问题是是否有其他优雅的实现方法可以做同样的事情。谢谢!

我认为你至少有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
    ...
    
    顺便说一下,这些导出的顺序似乎很重要:vector类模板在内部使用分配器类模板,因此分配器实例化必须在 vector实例化之前导出
  5. 直接使用内部函数可能是你最糟糕的选择,但是如果你封装了你的字段

    int *p_abc;
    int abc_len;
    

    class MyFancyDataArray

    字段
    char *p_str;
    int str_len;
    

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

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

我不知道这里你想解决哪个问题。有两个不同的问题:交叉编译器二进制兼容性和避免"未定义符号"链接器错误。你引用的C4251警告提到了第二个问题。这真的是一个主要的非问题,特别是与SCL类,如std::stringstd::vector。因为它们是在CRT中实现的,只要应用程序的双方使用相同的CRT,一切都将"正常工作"。在这种情况下,解决方案就是禁用警告。

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

使用指针指向实现(pImpl)的习惯用法