我正在尝试将一些代码移动到共享库中(在独立编译时工作正常)但是在使用类内联函数时遇到了一些问题。 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警告。
答案 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的调用将被内联,但在一般情况下我认为这是不可能的