Inline member functions in C++

2019-02-12 01:12发布

ISO C++ says that the inline definition of member function in C++ is the same as declaring it with inline. This means that the function will be defined in every compilation unit the member function is used. However, if the function call cannot be inlined for whatever reason, the function is to be instantiated "as usual". (http://msdn.microsoft.com/en-us/library/z8y1yy88%28VS.71%29.aspx) The problem I have with this definition is that it does not tell in which translation unit it would be instantiated. The problem I encountered is that when facing two object files in a single static library, both of which have the reference to some inline member function which cannot be inlined, the linker might "pick" an arbitrary object file as a source for the definition. This particular choice might introduce unneeded dependencies. (among other things)

For instance: In a static library

A.h:

class A{
  public:
    virtual bool foo() { return true; }
};

U1.cpp:

A a1;

U2.cpp:

A a2;

and lots of dependencies

In another project main.cpp:

#include "A.h"

int main(){
  A a;
  a.foo();
  return 0;
}

The second project refers the first. How do I know which definition the compiler will use, and, consequently which object files with their dependencies will be linked in? Is there anything the standard says on that matter? (Tried, but failed to find that)

Thanks

Edit: since I've seen some people misunderstand what the question is, I'd like to emphasize: If the compiler decided to create a symbol for that function (and in this case, it will, because of 'virtualness', there will be several (externally-seen) instantiations in different object file, which definition (from which object file?) will the linker choose?)

5条回答
相关推荐>>
2楼-- · 2019-02-12 01:22

Just my two cents. This is not about virtual function in particular, but about inline and member-functions generally. Maybe it is useful.

C++

As far as Standard C++ is concerned, a inline function must be defined in every translation unit in which it is used. And an non-static inline function will have the same static variables in every translation unit and the same address. The compiler/linker will have to merge the multiple definitions into one function to achieve this. So, always place the definition of an inline function into the header - or place no declaration of it into the header if you define it only in the implementation file (".cpp") (for a non-member function), because if you would, and someone used it, you would get a linker error about an undefined function or something similar.

This is different from non-inline functions which must be defined only once in an entire program (one-definition-rule). For inline functions, multiple definitions as outlined above are rather the normal case. And this is independent on whether the call is atually inlined or not. The rules about inline functions still matter. Whether the Microsoft compiler adheres to those rules or not - i can't tell you. If it adheres to the Standard in that regard, then it will. However, i could imagine some combination using virtual, dlls and different TUs could be problematic. I've never tested it but i believe there are no problems.

For member-functions, if you define your function in the class, it is implicitly inline. And because it appears in the header, the rule that it has to be defined in every translation unit in which it is used is automatically satisfied. However, if you define the function out-of-class and in a header file (for example because there is a circular dependency with code in between), then that definition has to be inline if you include the corresponding file more than once, to avoid multiple-definition errors thrown by the linker. Example of a file f.h:

struct f {
    // inline required here or before the definition below
    inline void g();
};

void f::g() { ... }

This would have the same effect as placing the definition straight into the class definition.

C99

Note that the rules about inline functions are more complicated for C99 than for C++. Here, an inline function can be defined as an inline definition, of which can exist more than one in the entire program. But if such a (inline-) definition is used (e.g if it is called), then there must be also exactly one external definition in the entire program contained in another translation unit. Rationale for this (quoting from a PDF explaining the rationale behind several C99 features):

Inlining in C99 does extend the C++ specification in two ways. First, if a function is declared inline in one translation unit, it need not be declared inline in every other translation unit. This allows, for example, a library function that is to be inlined within the library but available only through an external definition elsewhere. The alternative of using a wrapper function for the external function requires an additional name; and it may also adversely impact performance if a translator does not actually do inline substitution.

Second, the requirement that all definitions of an inline function be "exactly the same" is replaced by the requirement that the behavior of the program should not depend on whether a call is implemented with a visible inline definition, or the external definition, of a function. This allows an inline definition to be specialized for its use within a particular translation unit. For example, the external definition of a library function might include some argument validation that is not needed for calls made from other functions in the same library. These extensions do offer some advantages; and programmers who are concerned about compatibility can simply abide by the stricter C++ rules.

Why do i include C99 into here? Because i know that the Microsoft compiler supports some stuff of C99. So in those MSDN pages, some stuff may come from C99 too - haven't figured anything in particular though. One should be careful when reading it and when applying those techniques to ones own C++ code intended to be portable C++. Probably informing which parts are C99 specific, and which not.

A good place to test small C++ snippets for Standard conformance is the comeau online compiler. If it gets rejected, one can be pretty sure it is not strictly Standard conforming.

查看更多
Luminary・发光体
3楼-- · 2019-02-12 01:26

If the compiler decided to create a symbol for that function (and in this case, it will, because of 'virtualness', there will be several (externally-seen) instantiations in different object file, which definition (from which object file?) will the linker choose?)

The definition that is present in the corresponding translation unit. And a translation unit cannot, and I repeat, cannot have but exactly one such definition. The standard is clear about that.

[...]the linker might "pick" an arbitrary object file as a source for the definition.

EDIT: To avoid any further misunderstanding, let me make my point clear: As per my reading of the standard, the ability to have multiple definition across different TUs does not give us any practical leverage. By practical, I mean having even slightly varying implementations. Now, if all your TUs have the exact same definition, why bother which TU the definition is being picked up from?

If you browse through the standard you will find the One Definition Rule is applied everywhere. Even though it is allowed to have multiple definitions of an inline function:

3.2 One Definition Rule:

5 There can be more than one definition of a class type (Clause 9), concept (14.9), concept map (14.9.2), enumeration type (7.2), inline function with external linkage (7.1.2), [...]

Read it in conjunction with

3 [...] An inline function shall be defined in every translation unit in which it is used.

This means that the function will be defined in every compilation unit [...]

and

7.1.2 Function Specifiers

2 A function declaration (8.3.5, 9.3, 11.4) with an inline specifier declares an inline function. The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions defined by 7.1.2 shall still be respected.

3 A function defined within a class definition is an inline function. The inline specifier shall not appear on a block scope function declaration.[footnote: 82] If the inline specifier is used in a friend declaration, that declaration shall be a definition or the function shall have previously been declared inline.

and the footnote:

82) The inline keyword has no effect on the linkage of a function. § 7.1.2 138

as well as:

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. —end note ] If the definition of a function appears in a translation unit before its first declaration as inline, the program is ill-formed. 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 the body of an extern inline function is the same object in different translation units. [ Note: A string literal appearing in a default argument expression is not in the body of an inline function merely because the expression is used in a function call from that inline function. —end note ]

Distilled: Its ok to have multiple definitions, but they must have the same look and feel in every translation unit and address -- but that doesn't really give you much to cheer about. Having multiple deinition across translation units is therefore not defined (note: I am not saying you are invoking UB, yet).

As for the virtual thingy -- there won't be any inlining. Period.

The standard says:

  • The same declaration must be available
  • There must be one definition

From MSDN:

A given inline member function must be declared the same way in every compilation unit. This constraint causes inline functions to behave as if they were instantiated functions. Additionally, there must be exactly one definition of an inline function.

Your A.h contains the class definition and the member foo()'s definition.

U1.cpp and U2.cpp both define two different objects of class A.

You create another A object in main(). This is just fine.

So far, I have seen only one definition of A::foo() which is inline. (Remember that a function defined within the class declaration is always inlined whether or not it is preceded by the inline keyword.)

查看更多
Evening l夕情丶
4楼-- · 2019-02-12 01:27

AFAIK, there is no standard definition of how and when a C++ compiler will inline a function call. These are usually "recommendations" that the compiler is in no way required to follow. In fact, different users may want different behaviors. One user may care about speed, while another may care about small generated object file size. In addition, compilers and platforms are different. Some compilers may apply smarter analysis, some may not. Some compilers may generate longer code from the inline, or work on a platform where calls are too expensive, etc.

When you have an inline function, the compiler should still generate a symbol for it and eventually resolve a single version of it. So that if it is in a static library, people can still call the function not in inline. In other words, it still acts as a normal function,.

The only effect of the inline is that some cases, the compiler will see the call, see the inline, and skip the call completely, but the function should still be there, it's just not getting called in this case.

查看更多
该账号已被封号
5楼-- · 2019-02-12 01:35

Don't inline your functions if you want to ensure they get compiled into a specific library.

查看更多
不美不萌又怎样
6楼-- · 2019-02-12 01:41

When you have an inline method that is forced to be non-inlined by the compiler, it will really instantiate the method in every compiled unit that uses it. Today most compilers are smart enough to instantiate a method only if needed (if used) so merely including the header file will not force instantiation. The linker, as you said, will pick one of the instantiations to include in the executable file - but keep in mind that the record inside the object module is of a special kind (for instance, a COMDEF) in order to give the linker enough information to know how to discard duplicated instances. These records will not, therefore, result in unwanted dependencies between modules, because the linker will use them with less priority than "regular" records to resolve dependencies.

In the example you gave, you really don't know, but it doesn't matter. The linker won't resolve dependencies based on non-inlined instances alone ever. The result (in terms of modules included by the linker) will be as good as if the inline method didn't exist.

查看更多
登录 后发表回答