内联类函数和共享库(dll)构建

时间:2013-03-28 08:07:54

标签: c++ mingw shared-libraries inline dllexport

我正在尝试将一些代码移动到共享库中(在独立编译时工作正常)但是在使用类内联函数时遇到了一些问题。 mingw / gcc v4.7.2。

部分问题似乎是因为我更喜欢在类声明之外定义我的内联函数(它使类声明更整洁,更容易阅读)。我一直认为这是可以接受的,等同于在类声明中定义......但似乎并非总是如此。我已经创建了一个简单的示例来演示问题。 (显然dllexport通常会在宏中导入/导出之间切换。)

部首:

// Uncomment one at a time to see how it compiles with: -O2 -Winline
//#define INLINE_OPTION 1 // implicit - builds without inline warnings
#define INLINE_OPTION 2 // simple external inline - gives inline warnings
//#define INLINE_OPTION 3 // external forced inline - gives inline errors

class __attribute__((dllexport)) Dummy {
public:
    Dummy() : m_int{0} {}
    ~Dummy() {}
    #if INLINE_OPTION == 1
    int get_int() const { return m_int; }
    #else
    int get_int() const;
    #endif
    int do_something();
private:
    int m_int;
};

#if INLINE_OPTION == 2
inline int Dummy::get_int() const
{ return m_int; }
#endif

#if INLINE_OPTION == 3
inline __attribute__((always_inline)) int Dummy::get_int() const
{ return m_int; }
#endif

.cpp文件:

int Dummy::do_something()
{
    int i = get_int();
    i *= 2;
    return i;
}

如上所述,使用INLINE_OPTION == 1(隐式的,类内内联定义),代码将编译出警告。

使用INLINE_OPTION == 2(类外的内联定义),我收到此警告:int Dummy::get_int() const' can never be inlined because it uses attributes conflicting with inlining [-Winline]

使用INLINE_OPTION == 3(试图强制内联),我得到与上面相同的警告,我收到此错误:error: inlining failed in call to always_inline 'int Dummy::get_int() const': function not inlinable,其中有关它的信息是从Dummy内的第一行调用的:: .cpp文件中的do_something()。请注意,这是关于尝试内联库本身的功能!对于简单的访问器功能,这可能是一个非常重要的开销。

我做错了吗? gcc是否正确地将类外定义内联函数与内部函数定义区别对待? (我真的被迫弄乱了班级宣言吗?)

注意:问题不仅影响我内联声明的内容。它还会影响声明为constexpr的任何内容,甚至在涉及继承时声明为“= default”的析构函数。

修改

尝试使用mingw64 / gcc v4.8.0获得相同的结果。请注意,这包括选项1不在do_something中内联的事实(我检查了汇编程序输出),因此显然选项1和选项2之间的唯一区别是只有选项2会给出-Winline警告。

6 个答案:

答案 0 :(得分:2)

我对如何在Windows上创建共享库一无所知。在linux / OSX中,源代码中不需要特殊处理,因此共享(.so)和普通(.a)库可以使用相同的源代码而无需特殊处理。

如果您确实需要将符号的特殊属性导出到共享库中,那么您可以简单地拆分代码,例如

namespace implementation_details {
  class __attribute__((dllexport)) DummyBase
  {
  protected:
    DummyBase() : m_int{0} {}
    ~DummyBase() {}
    int do_something();
    int m_int;
  };
}

struct Dummy: private implementation_details::DummyBase
{
  using implementation_details::DummyBase::do_something;
  int get_int() const noexcept;
};

inline __attribute__((always_inline)) int Dummy::get_int() const noexcept
{ return m_int; }

答案 1 :(得分:1)

好吧也许我的回答有点神秘......让我举一个快速举例说明我的意思是使用你的代码片段。

dummy.h:

#ifndef _DUMMY_H_
#define _DUMMY_H_

class __attribute__((dllexport)) Dummy {
public:
  Dummy() : m_int{0} {}
  ~Dummy() {}
  int get_int() const;
  int do_something();
private:
  int m_int;
};

// here goes the include of the implementation header file
#include "dummy.h.impl"
#endif // _DUMMY_H_

dummy.h.impl:

// there will be no symbol for Dummy::get_int() in the dll.
// Only its contents are copied to the places where it
// is used. Placing this in the header gives other binaries
// you build with this lib the chance to do the same.
inline int Dummy::get_int() const
{ return m_int; }

当然,您可以将类声明下方的内联定义放在同一个头文件中。但是,我发现这仍然违反了声明和定义的分离。

dummy.cpp:

// this method will become a symbol in the library because 
// it is a C++ source file.
int Dummy::do_something()
{
  // i would if i knew what to do...
  return 0;
}

希望我能提供帮助。

答案 2 :(得分:1)

我在其他帖子上做的编辑似乎没有采取,无论如何,似乎一些额外的清晰度可能是适当的,所以我发布了我发送到另一个论坛的详细信息。在下面的代码中class C是解决此问题的方法 - 只导出非内联成员,而不是整个类。如其他地方的评论所述,__declspec(dllexport)__attribute__((dllexport))是等效的。

test.hpp

class __declspec(dllexport) A {
public:
    int fa() { return m; }
    int ga();
private:
    int m{0};
};

class __declspec(dllexport) B {
public:
    int fb();
    int gb();
private:
    int m{0};
};
inline int B::fb() { return m; }

class C {
public:
    int fc() { return m; }
    __declspec(dllexport) int gc();
private:
    int m{0};
};

TEST.CPP

#include "test.hpp"

int A::ga() { return (fa() + 1); }

int B::gb() { return (fb() + 1); }

int C::gc() { return (fc() + 1); }

如果使用选项-std=c++11 -O2 -S -Winline(使用mingw / ming64和gcc v4.7.2或v4.8.0)编译它,您可以看到为库函数ga,gb和gc生成的汇编程序如下所示:< / p>

GA

subq    $40, %rsp
.seh_stackalloc 40
.seh_endprologue
call    _ZN1A2faEv
addl    $1, %eax
addq    $40, %rsp
ret

GB:

subq    $40, %rsp
.seh_stackalloc 40
.seh_endprologue
call    _ZN1B2fbEv
addl    $1, %eax
addq    $40, %rsp
ret

GC

.seh_endprologue
movl    (%rcx), %eax
addl    $1, %eax
ret

你得到警告:
warning: function 'int B::fb()' can never be inlined because it uses attributes conflicting with inlining [-Winline]
warning: inlining failed in call to 'int B::fb()': function not inlinable [-Winline] (called from B::gb())

请注意,没有关于fa没有内联的警告(我认为,这是预期的)。但是请注意ga,gb和gc都是库函数。无论您如何考虑是否应该导出内联函数本身,都没有充分的理由说明为什么内联不能在库中内联。因此我认为这是编译器中的一个错误。

浏览一下备受好评的代码,看看你发现只输出显式成员的数量。例如,将编译到库中的少数boost部分(例如:regex)使用class A技术,这意味着库中没有内联许多访问器函数。

但是,除此之外,现在的答案是class C技术(显然在实际代码中,这必须包含在宏中,以便像通常在类级别一样在导出和导入之间切换)。

答案 3 :(得分:1)

这不是一个编译器错误,正如一些人所建议的那样。在C ++中,如果函数是内联的,则必须在每个声明中声明内联。有5个属性必须满足,其中之一是:

 An inline function with external linkage (e.g. not declared static) has the following additional properties:
1) It must be declared inline in every translation unit.
...

在您的示例中,您首先将函数Dummy :: get_int()声明为非内联类定义。这意味着函数不能重新声明为内联

来源:http://en.cppreference.com/w/cpp/language/inline

BTW:内联说明符在C中的工作方式不同,您可以在其中声明同一函数的内联和非内联版本。但是,你必须实现两者并确保他们做同样的事情。

答案 4 :(得分:0)

为什么不在类声明(inline int get_int() const;)中声明函数内联?也许有错误?

答案 5 :(得分:0)

编译器无法内联必须在dll中导出的函数。毕竟当从与你的dll链接的可执行文件调用时,该函数应该有一个地址。很可能来自do_something的调用将被内联,但在一般情况下我认为这是不可能的