6

我正在尝试将一些代码移动到共享库中(独立编译时工作正常),但遇到类内联函数的一些问题。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,有关它的信息是从 .cpp 文件中的 Dummy::do_something() 内的第一行调用的. 请注意,这是关于尝试在库本身内内联函数!对于简单的访问器函数,这可能是非常重要的开销。

难道我做错了什么?将类外定义内联函数与类内函数定义区别对待,gcc 是否正确?(我真的被迫弄乱类声明吗?)

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

编辑:

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

4

6 回答 6

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; }
于 2013-03-29T12:51:34.017 回答
1

好吧,也许我的回答有点神秘......让我给你一个简单的例子,说明我的意思是使用你的代码片段。

假人.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; }

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

虚拟.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;
}

希望我能有所帮助。

于 2013-03-28T17:19:24.493 回答
1

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

测试.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};
};

测试.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 生成的汇编程序如下所示:

嘎:

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

国标:

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 部分(例如:正则表达式),使用该class A技术,这意味着许多访问器函数没有被内联到库中。

但是,除此之外,现在的答案是class C技术(显然在实际代码中,它必须包含在宏中才能在导出和导入之间切换,就像您通常在类级别所做的那样)。

于 2013-03-30T00:27:54.157 回答
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

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

于 2016-02-23T08:28:47.977 回答
0

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

于 2013-03-28T10:14:10.500 回答
0

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

于 2013-03-28T15:03:15.870 回答