22
votes

It is not a good practice using stl-classes in the dll-interface as Common practice in dealing with warning c4251: class … needs to have dll-interface explains. An example is given:

#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;

};

When compiling this file, the following warnings can be observed:

 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'

Then the question is how we can implement the same functionality without using STL class vector and string. One implementation I could think of is as follows:

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;



};

As you can see, in the new implementation we use int *p_abc to substitute vector abc and char *p_str to replace string str. The question I have is whether there are other elegant implementation approaches that can do the same. Thanks!

4

4 Answers

30
votes

I think you've got at least 5 possible options to solve this problem:

  1. You could still keep STL/template classes for your fields and also use them as parameter types, as long as you keep all the fields private (which is a good practice anyway). Here is a discussion about this. To remove the warnings you could simply use according #pragma statements.

  2. You could create small wrapper classes with private STL fields and expose fields of your wrapper class types to the public instead, but this would most probably only move the warnings to another places.

  3. You could use the PIMPL idiom to hide your STL fields in a private implementation class, which will not even be exported from your library nor be visible outside of it.

  4. Or you could actually export all the required template class specializations, as suggested in the C4251 warning, in a way that is described here. In short you would have to insert following lines of code before your HelloWorld class:

    ...
    #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
    ...
    

    By the way, the order of those exports seems to be important: the vector class template uses the allocator class template internally, so the allocator instantiation has to be exported before the vector instantiation.

  5. The direct use of intrinsics is probably your worst option, but if you encapsulate your fields

    int *p_abc;
    int abc_len;
    

    in something like class MyFancyDataArray and the fields

    char *p_str;
    int str_len;
    

    in something like class MyFancyString, then this would be a more decent solution (similar to the one described at the second point). But it is probably not the best habit to reinvent the wheel too often.

8
votes

OR do the easiest thing to do, move the __declspec to the ONLY members you care to export:

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;
};
7
votes

I'm not sure which problem you want to solve here. There are two different issues: Cross compiler binary compatibility and avoiding "undefined symbol" linker errors. The C4251 warning you quoted talks about the second issue. Which is really mostly a non-issue, especially with SCL classes like std::string and std::vector. Since they're implemented in the CRT, as long as both sides of your application use the same CRT, everything will "just work". The solution in that case is to just disable the warning.

Cross compiler binary compatibility OTOH, which is what is discussed in the other stackoverflow question you linked, is a whole different beast. For that to work out, you basically have to keep all your public header files free of any mention of any SCL class. Which means you either have to PIMPL everything or use abstract classes everywhere (or a mix thereof).