如果内联是可选的,为什么删除“inline”会导致错误? 导致链接器错误?

发布于 2024-07-22 04:41:47 字数 638 浏览 13 评论 0原文

我有一个具有内联成员的类,但后来我决定要从标头中删除实现,因此我将函数的成员主体移到了 cpp 文件中。 起初我只是在头文件中留下了内联签名(我太草率了),程序无法正确链接。 然后我修复了我的标题,当然一切都正常。

但是 inline 不是完全可选的吗?

在代码中:

First:

//Class.h
class MyClass
{
   void inline foo()
   {}
};

Next 更改为 (不会链接):

//Class.h
class MyClass
{
   void inline foo();
};

//Class.cpp
void MyClass::foo()
{}

然后更改为 (会正常工作):

//Class.h
class MyClass
{
   void foo();
};

//Class.cpp
void MyClass::foo()
{}

我认为 inline 是可选的,并想象我可能会得到对我的草率提出警告,但没想到会出现链接错误。 在这种情况下,编译器应该做的正确/标准的事情是什么?根据标准,我应该承担我的错误吗?

I have a class that had an inline member, but I later decided that I wanted to remove the implementation from the headers so I moved the members body of the functions out to a cpp file. At first I just left the inlined signature in the header file (sloppy me) and the program failed to link correctly. Then I fixed my header and it all works fine, of course.

But wasn't inline totally optional?

In code:

First:

//Class.h
class MyClass
{
   void inline foo()
   {}
};

Next changed to (won't link):

//Class.h
class MyClass
{
   void inline foo();
};

//Class.cpp
void MyClass::foo()
{}

And then to (will work fine):

//Class.h
class MyClass
{
   void foo();
};

//Class.cpp
void MyClass::foo()
{}

I thought inline was optional, and imagined I might get by with a warning for my sloppiness, but didn't expect a linking error. What's the correct/standard thing a compiler should do in this case, did I deserve my error according to the standard?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

橘和柠 2024-07-29 04:41:47

事实上,有这样一个定义规则:内联函数必须在它使用的每个翻译单元中定义。 血淋淋的细节如下。 第一个 3.2/3

每个程序都应包含该程序中使用的每个非内联函数或对象的一个​​定义; 无需诊断。 该定义可以显式地出现在程序中,可以在标准或用户定义的库中找到,或者(在适当的情况下)它是隐式定义的(参见12.1、12.4和12.8)。
内联函数应在使用它的每个翻译单元中定义。

当然还有 7.1.2/4

内联函数应在使用它的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义(3.2)。 [注意:在其定义出现在翻译单元中之前,可能会遇到对内联函数的调用。 ] 如果具有外部链接的函数在一个翻译单元中声明为内联,则应在它出现的所有翻译单元中声明为内联; 无需诊断。 具有外部链接的内联函数在所有翻译单元中应具有相同的地址。 外部内联函数中的静态局部变量始终引用同一个对象。 外部内联函数中的字符串文字是不同翻译单元中的同一对象。

但是,如果您在类定义中定义函数,则它会隐式声明为 inline 函数。 这将允许您在程序中多次包含包含该内联函数体的类定义。 由于该函数具有外部链接,因此它的任何定义都将引用相同函数(或更血淋淋的 - 相同实体)。

关于我的主张的血淋淋的细节。 第一个 3.5/5

此外,如果类的名称具有外部链接,则成员函数、静态数据成员、类或类范围的枚举也具有外部链接。

然后3.5/4

具有命名空间范围的名称具有外部链接,如果它是命名类(第 9 条)的名称,或者是在 typedef 声明中定义的未命名类,其中该类具有用于链接目的的 typedef 名称.

这个“用于链接目的的名称”是一件有趣的事情:

typedef struct { [...] } the_name;

由于现在您的程序中对同一实体有多个定义,ODR 的另一件事恰好限制了您。 3.2/5 接下来是无聊的东西。

程序中可以有多个类类型(第 9 条)、枚举类型(7.2)、具有外部链接的内联函数(7.1.2)[...] 的定义,前提是每个定义都出现在不同的翻译单位,且定义满足以下要求。 给定这样一个名为 D 的实体在多个翻译单元中定义,那么

  • D 的每个定义应由相同的标记序列组成; 和
  • 在 D 的每个定义中,根据 3.4 查找的相应名称应引用 D 定义内定义的实体,或者在重载决策(13.3)和部分模板匹配之后应引用相同的实体专业化 (14.8.3) [...]

我现在剪掉了一些不重要的东西。 以上是关于内联函数需要记住的两个重要内容。 如果您多次定义外部内联函数,但以不同的方式定义它,或者如果您定义它并且其中使用的名称解析为不同的实体,那么您将执行未定义的行为。

必须在使用该函数的每个 TU 中定义该函数的规则很容易记住。 而且它是相同的也很容易记住。 但是名称解析呢? 这里有一些例子。 考虑一个静态函数 assert_it

static void assert_it() { [...] }

现在,由于 static 会给它内部链接,当你将它包含到多个翻译单元中时,每个定义都会定义一个不同的实体。 这意味着您不允许从将在程序中多次定义的外部内联函数中使用assert_it:因为发生的情况是内联函数将引用一个 TU 中名为 assert_it 的实体,但另一个 TU 中另一个同名的实体。 您会发现这一切都是无聊的理论,编译器可能不会抱怨,但我发现这个示例特别显示了 ODR 和实体之间的关系。


接下来再次回到您的特定问题。

以下是相同的内容:

struct A { void f() { } };
struct A { inline void f(); }; void A::f() { } // same TU!

但这一个不同,因为该函数是非内联的。 您将违反 ODR,因为如果多次包含标头,您就有多个 f 定义

struct A { void f(); }; void A::f() { } // evil!

现在,如果您将 inline 放在 f 的声明上 在类中,但随后省略在标头中定义它,那么您就违反了 3.2/3 (和 7.1.2/4 说的是同样的事情,只是更详细),因为该函数未在该翻译单元中定义!

请注意,在 C (C99) 中,内联具有与 C++ 中不同的语义。 如果你创建一个外部内联函数,你应该首先阅读一些好的论文(最好是标准),因为这些在 C 中确实很棘手(基本上,任何使用的函数的内联定义都需要另一个非内联函数定义在另一个TU. C 中的静态内联函数很容易处理,除了 C 和 C++ 中通常的“内联替换”提示仅用作内联之外。由于 static 在任何时候使用时都会创建一个不同的实体(由于内部链接),因此 inline 将仅添加内联替换提示 - 而不是更多。

Indeed, there is this one definition rule saying that an inline function must be defined in every translation unit it is used. Gory details follow. First 3.2/3:

Every program shall contain exactly one definition of every non-inline function or object that is used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8).
An inline function shall be defined in every translation unit in which it is used.

And of course 7.1.2/4:

An inline function shall be defined in every translation unit in which it is used and shall have exactly the same definition in every case (3.2). [Note: a call to the inline function may be encountered before its definition appears in the translation unit. ] If a function with external linkage is declared inline in one translation unit, it shall be declared inline in all translation units in which it appears; no diagnostic is required. An inline function with external linkage shall have the same address in all translation units. A static local variable in an extern inline function always refers to the same object. A string literal in an extern inline function is the same object in different translation units.

However, if you define your function within the class definition, it is implicitly declared as inline function. That will allow you to include the class definition containing that inline function body multiple times in your program. Since the function has external linkage, any definition of it will refer to the same function (or more gory - to the same entity).

Gory details about my claim. First 3.5/5:

In addition, a member function, static data member, class or enumeration of class scope has external linkage if the name of the class has external linkage.

Then 3.5/4:

A name having namespace scope has external linkage if it is the name of [...] a named class (clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes.

This "name for linkage purposes" is this fun thing:

typedef struct { [...] } the_name;

Since now you have multiple definitions of the same entity in your programs, another thing of the ODR happens to restrict you. 3.2/5 follows with boring stuff.

There can be more than one definition of a class type (clause 9), enumeration type (7.2), inline function with external linkage (7.1.2) [...] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens; and
  • in each definition of D, corresponding names, looked up according to 3.4, shall refer to an entity defined within the definition of D, or shall refer to the same entity, after overload resolution (13.3) and after matching of partial template specialization (14.8.3) [...]

I cut off some unimportant stuff now. The above are the two important one to remember about inline functions. If you define an extern inline function multiple times, but do define it differently, or if you define it and names used within it resolve to different entities, then you are doing undefined behavior.

The rule that the function has to be defined in every TU in which it is used is easy to remember. And that it is the same is also easy to remember. But what about that name resolution thingy? Here some example. Consider a static function assert_it:

static void assert_it() { [...] }

Now, since static will give it internal linkage, when you include it into multiple translation units, then each definition will define a different entity. This means that you are not allowed to use assert_it from an extern inline function that's going to be defined multiple times in the program: Because what happens is that the inline function will refer to one entity called assert_it in one TU, but to another entity of the same name in another TU. You will find that this all is boring theory and compilers won't probably complain, but i found this example in particular shows the relation between the ODR and entities.


What follows is getting back to your particular problem again.

Following are the same things:

struct A { void f() { } };
struct A { inline void f(); }; void A::f() { } // same TU!

But this one is different, since the function is non-inline. You will violate the ODR, since you have more than one definition of f if you include the header more than once

struct A { void f(); }; void A::f() { } // evil!

Now if you put inline on the declaration of f inside the class, but then omit defining it in the header, then you violate 3.2/3 (and 7.1.2/4 which says the same thing, just more elaborating), since the function isn't defined in that translation unit!

Note that in C (C99), inline has different semantics than in C++. If you create an extern inline function, you should first read some good paper (preferably the Standard), since those are really tricky in C (basically, any used inline-definition of a function will need another, non-inline function definition in another TU. static inline functions in C are easy to handle. They behave like any other function, apart of having the usual "inline substitution" hint. static inline in both C and C++ serve only as a inline-substitution hint. Since static will already create a different entity any time it's used (because of internal linkage), inline will just add the inline-substitution hint - not more.

辞取 2024-07-29 04:41:47

该方法是否实际内联由编译器自行决定。 然而inline关键字的存在也会影响方法的链接。

C++ 链接不是我的专长,因此我将遵循链接以获得更好的解释。

或者,您也可以等待litb 在一个小时左右提供血淋淋的细节;)

Whether or not the method is actually inlined is at the sole discretion of the compiler. However the presence of the inline keyword will also affect the linkage of the method.

C++ linkage is not my specialty so I'll defer to the links for a better explanation.

Alternately you can just wait for litb to provide the gory details in an hour or so ;)

桃酥萝莉 2024-07-29 04:41:47

需要注意的是:当方法被声明为内联时,它的定义必须与其声明在一起。

Point to note: when method is declared inline, its definition MUST be together with its declaration.

三岁铭 2024-07-29 04:41:47

关于harrath.jr的答案,如果方法的定义具有“inline”关键字,并且该定义在同一标头中可用,则不需要声明内联方法,ie

class foo
{
  void bar();
};

inline void foo::bar()
{
  ...
}

这对于有条件内联方法很有用取决于构建是“debug”还是“release”,如下所示:

// Header - foo.h

class foo
{
  void bar();  // Conditionally inlined.
};

#ifndef FOO_DEBUG
# include "foo.inl"
#endif

“内联”文件可能如下所示:

// Inline Functions/Methods - foo.inl
#ifndef FOO_DEBUG
# define FOO_INLINE inline
#else
# define FOO_INLINE
#endif

FOO_INLINE void foo::bar()
{
  ...
}

并且实现可能如下所示

// Implementation file - foo.cpp
#ifdef FOO_DEBUG
# include "foo.inl"
#endif

...

:不太漂亮,但当激进的内联成为调试难题时,它有它的用处。

Regarding harshath.jr's answer, a method need not be declared inline if its definition has the "inline" keyword, and that definition is available in the same header, i.e.:

class foo
{
  void bar();
};

inline void foo::bar()
{
  ...
}

This is useful for conditionally inlining a method depending on whether or not the build is "debug" or "release" like so:

// Header - foo.h

class foo
{
  void bar();  // Conditionally inlined.
};

#ifndef FOO_DEBUG
# include "foo.inl"
#endif

The "inline" file could look like:

// Inline Functions/Methods - foo.inl
#ifndef FOO_DEBUG
# define FOO_INLINE inline
#else
# define FOO_INLINE
#endif

FOO_INLINE void foo::bar()
{
  ...
}

and the implementation could like the following:

// Implementation file - foo.cpp
#ifdef FOO_DEBUG
# include "foo.inl"
#endif

...

It's not exactly pretty but it has it's uses when aggressive inline becomes a debugging headache.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文