【问题标题】:dllexport class template instances (specializations), reducing compilation time for header-only template librariesdllexport 类模板实例(专业化),减少仅头文件模板库的编译时间
【发布时间】:2019-11-30 19:18:48
【问题描述】:

是否可以导出一些类模板实例,同时让库的用户能够生成给定类模板的其他特化(编译可执行文件时)。

鉴于我有一个公共标头

// public.h
#pragma once

#ifndef DLL_BUILD
#define API __declspec(dllimport)
#else 
#define API __declspec(dllexport)
#endif // !DLL_BUILD

#include <type_traits>


// dummy to generate .lib
struct API dummy
{
    void be_dummy();
};



template <class T>
struct Foo
{
    static T Sum(T a, T b)
    {
        static_assert(std::is_fundamental_v<T>);
        return a + b;
    }
};


通过这种声明类模板Foo 的方式,每个实例化都将发生在用户的可执行文件中。

但是,如果我使用 API 宏将 Foo 定义为 dllexport/dllimport,则尚未在 dll 中显式实例化的 Foo 的每个特化都将无法链接。

// impl.cpp - dll

#include "public.h"


void dummy::be_dummy()
{
    volatile int a = 0;
    return;
}

template API struct Foo<int>;


///////////////////////////////////////////
// main.cpp - executable

#include "public.h"

#include <iostream>

int main()
{
    dummy().be_dummy();

    // std::cout << Foo<double>().Sum(4.12, 3.18) << std::endl; // Unresolved external symbol

    std::cout << Foo<int>().Sum(60, 9) << std::endl; // executed within the dll

    return 0;
}

那么,是否可以强制编译器在一个已导出的类模板实例上链接到现有的类模板实例,并生成另一个尚未导出的类模板实例。

更新

我找到了解决方案,请参阅下面的答案。 我保留旧的更新,以防有人会发现 SFINAE 的这种用法有帮助。


更新旧版本

我找到了一个涉及 SFINAE 的繁琐解决方案,但它导致定义一个类模板两次,因此非常容易出错。我不知道它是否可以用宏包裹起来,这样就可以只写一次。

// public.h
#pragma once

#ifndef DLL_BUILD
#define API __declspec(dllimport)
#else 
#define API __declspec(dllexport)
#endif // !DLL_BUILD

#include <type_traits>

namespace templ_export
{
    template <class T>
    struct is_exported : std::false_type {};

    // this can be placed to a separated header (i.e. Exported.hpp)
    template <> struct is_exported<int> : std::true_type {};

    template <class T>
    struct API FooExported
    {
        static T Sum(T a, T b)
        {
            //static_assert(std::is_fundamental_v<T>);
            return a + b;
        }
    };

    template <class T>
    struct FooNotExported
    {
        static T Sum(T a, T b)
        {
            //static_assert(std::is_fundamental_v<T>);
            return a + b;
        }
    };


    template <class T, bool = templ_export::is_exported<T>()>
    struct GetFooExported
    {
        using type = FooNotExported<T>;
    };

    template <class T>
    struct GetFooExported<T, true>
    {
        using type = FooExported<T>;
    };
}


template <class T>
using Foo = typename templ_export::GetFooExported<T>::type;


/////////////////////////////////
// impl.cpp

#include "public.h"


void dummy::be_dummy()
{
    volatile int a = 0;
    return;
}

template struct API templ_export::FooExported<int>;

【问题讨论】:

    标签: c++ dll class-template template-instantiation compilation-time


    【解决方案1】:

    这是导出类模板实例的简单方法。

    在创建 Dll 时编译器必须认为,Foo 被定义为 dllexport。但在创建可执行文件并链接到该 Dll 时,Foo 类模板不得应用任何 declspec 属性。虽然我们需要将特定的类模板实例声明为dllimport

    // public.h
    #pragma once
    
    #ifndef DLL_BUILD
    #define API __declspec(dllimport)
    #else 
    #define API __declspec(dllexport)
    #endif // !DLL_BUILD
    
    // define T_API emplty for library users, hence they will see just 'struct Foo'
    #ifndef T_API
    #define T_API
    #endif
    
    #include <type_traits>
    
    template <class T>
    struct T_API Foo
    {
        static T Sum(T a, T b)
        {
            //static_assert(std::is_fundamental_v<T>);
            return a + b;
        }
    };
    
    
    // impl.cpp
    // Compile with DLL_BUILD defined
    
    // define T_API for library build
    #define T_API __declspec(dllexport)
    
    #include "public.h"
    
    
    void dummy::be_dummy()
    {
        volatile int a = 0;
        return;
    }
    
    // instantiating class template 
    template struct T_API Foo<int>;
    
    

    对于可执行文件:

    // Exported.h
    // this header needs to be shipped alongside with public.h and included after
    #pragma once
    
    // declare template instance as imported
    template struct __declspec(dllimport) Foo<int>;
    
    // main.cpp 
    // Executable linked to library
    
    #include "public.h"
    #include "Exported.h"
    
    
    
    int main()
    {
        dummy().be_dummy();
    
        // Sum is called from Executable
        std::cout << Foo<double>().Sum(4.12, 3.18) << std::endl;
    
        // Sum is called from Dll
        std::cout << Foo<int>().Sum(60, 9) << std::endl;
    
    
        return 0;
    }
    
    

    我认为这种方法对于仅包含标头的模板库很有用。与预编译头文件一样,带有类模板实例的 dll 将减少编译时间。

    【讨论】:

      猜你喜欢
      • 2023-03-28
      • 1970-01-01
      • 2010-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多