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



我试图将一些代码移动到共享库中(在独立编译时工作良好),但获得类内联函数的一些问题。mingw/gcc v4.7.2。

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

// 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 (out- class inline definition)我得到这个警告: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,关于它的信息是从.cpp文件中的Dummy::do_something()中的第一行调用的。注意,这是关于在库本身内嵌函数的尝试!对于简单的访问器函数,这可能是一个非常非常大的开销。

我做错了什么吗?在处理类外定义内联函数不同于类内函数定义是正确的吗?(我真的要把类声明弄得乱七八糟吗?)

注意:这个问题不仅影响我声明内联的东西。当涉及到继承时,它还会影响任何声明为constexpr的函数,甚至是声明为"= default"的析构函数。

编辑:

刚刚尝试了与mingw64/gcc v4.8.0相同的结果。请注意,这包括选项1不内联do_something(我检查了汇编器输出)的事实,所以显然选项1和选项2之间的唯一区别是只有选项2会给出-Winline警告。

我对如何在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; }

好吧,也许我的回答有点神秘…让我给你一个快速的例子,我的意思是使用你的代码片段。

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

希望我能帮上忙。

我在另一个帖子上所做的编辑似乎没有被接受,无论如何,似乎一些额外的清晰度可能是合适的,所以我把细节发到了另一个论坛。在下面的代码中,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生成的汇编器如下所示:

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技术(显然,在实际代码中,它必须包含在宏中,以便在导出和导入之间切换,就像您通常在类级别上那样)。

这不是编译器错误,正如一些人所建议的那样。在c++中,if函数是内联的,它必须在每个声明中都声明为内联的。有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

顺便说一句:内联说明符在C中的工作方式不同,在C中可以声明同一函数的内联和非内联版本。但是,您必须同时实现这两个功能,并确保它们做同样的事情。

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

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

相关内容

  • 没有找到相关文章

最新更新