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

fee*_*ree 20 c++ visual-studio-2010

在dll-interface中使用stl-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;

};
Run Code Online (Sandbox Code Playgroud)

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

 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'
Run Code Online (Sandbox Code Playgroud)

那么问题是我们如何在不使用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;



};
Run Code Online (Sandbox Code Playgroud)

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

buy*_*ush 25

我认为你至少有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
    ...
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    int *p_abc;
    int abc_len;
    
    Run Code Online (Sandbox Code Playgroud)

    在类似的东西class MyFancyDataArray和领域

    char *p_str;
    int str_len;
    
    Run Code Online (Sandbox Code Playgroud)

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

  • @Simon:使用这个`模板类__declspec(dllexport)std :: basic_string <char>;` (2认同)

Rod*_*dez 7

或做最简单的事情,移动__declspec到您想要导出的ONLY成员:

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;
};
Run Code Online (Sandbox Code Playgroud)

  • 只导出一些方法而不导出整个类真的有效吗? (2认同)
  • 从 dll 导出单个方法时,您需要小心控制对非导出成员的访问,因为编译器(对于 exe)会很乐意在 dll 的“另一侧”为它们生成适当的代码并调用从 dll 导出的函数。访问控制需要扩展到成员变量和特殊成员(如复制和赋值);最好的办法是将非导出成员全部设为私有(或 `=delete`)。 (2认同)
  • @KeithRussell 这对于像“标量删除析构函数”和“向量删除析构函数”这样的函数是如何工作的?当您“dllexport”dtor 时,它们会自动导出吗? (2认同)

Pau*_*oke 6

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

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