0

I've been having some problems with declaring functions inline causing unresolved external reference linker errors. I must be misunderstanding something quirky about C++. I'm trying to reduce the compile time of my C++ SDK using a 3-file translation unit where there is one "codeless header" that has only declarations and no implementations, another "code header" that contains all of the templates with implementations, and a unique .cpp filename to minimize hashtable collisions. I'm trying to make either a statically compiled library, DLL, or compile directly into an executable. I want my functions to be inlined, but the problem is that this super basic code will not compile:

// in pch.h
#include <iostream>
#ifdef ASSEMBLE_DYNAMIC_LIB
#ifdef LIB_EXPORT
#define LIB_MEMBER __declspec(dllexport)
#else
#define LIB_MEMBER __declspec(dllimport)
#endif
#else
#define LIB_MEMBER
#endif

// in foo.h
#pragma once
#include "pch.h"
struct LIB_MEMBER Foo {
   Foo ();
   inline int Bar (); //< inline causes Unresolved external reference error???
};

// in foo.cpp
#include "foo.h"
Foo::Foo () {}
int Foo::Bar()

// main.cpp
#include "foo.h"

int main(int argv, char** args) {
  Foo foo;
  std::cout << "Hello StackOverflow. foo is " << foo.Bar();
  while (1)
    ;
}

The code results in this linker error:

Severity Code Description Project File Line Suppression State Error LNK2019 unresolved external symbol "public: int __cdecl Foo::Bar(void)" (?Bar@Foo@@QEAAHXZ) referenced in function main experiments C:\workspace\kabuki_toolkit\projects\experiments\main.obj 1

All of the code I've found on StackOverflow won't compile with the same error. For example:

// in foo.cpp
#include "foo.h"
Foo::Foo () {}
inline int Foo::Bar() {} //< Again, Unresolved external reference error :-(

The Visual-C++ documetnation has some stuff about how to inline a DLL class member, but they have no code examples.

13
  • 4
    Exported functions cannot be inline - why do you think this is the case? Commented Aug 12, 2019 at 1:32
  • 2
    The error message you cite doesn't appear to bear any relation to the code you've shown. Commented Aug 12, 2019 at 1:40
  • They all do that. I've got hundreds of functions that do the same error message. Commented Aug 12, 2019 at 1:42
  • Quote from Microsoft: "You can define as inline a function with the dllexport attribute." Commented Aug 12, 2019 at 1:44
  • Maybe it's that I have to declare it as not inline int he "codless header" and put the declaration in the "code header" declared inline? Commented Aug 12, 2019 at 1:49

4 Answers 4

1

In C++ inline functions must have their body present in every translation unit from which they are called, otherwise the program is ill-formed. (Ref: C++17 [basic.def.odr]/4)

Marking the class as dllexport or dllimport does not escape that requirement.

NB. The semantics of inline functions in exported classes are described on this MSDN page ; it's exported the same way as a non-inline function; and the importing compiler can choose whether to use the inline definition or the imported definition.

To fix this you could declare Bar as non-inline; or provide the body in the header, e.g.

struct LIB_MEMBER Foo {
    Foo ();
    void Bar () { } 
};

or, equivalently,

struct LIB_MEMBER Foo {
    Foo ();
    inline void Bar () { } 
};

inline void Foo::Bar() {}

As mentioned by the MSDN page, having an exported class with an inline function means that you can't safely change the function in a later release of the DLL without recompiling the client.

Sign up to request clarification or add additional context in comments.

Comments

0

You've set ASSEMBLE_DYNAMIC_LIB to NO_0 in the header, which means all of the export flags are ignored. Your pre-processor usage is also wrong: You probably don't want to be using #ifdef, and should be using #if when posing a question...

#if ASSEMBLE_DYNAMIC_LIB == YES_0

If you intend for void Foo::Bar() {} to be inline, then move it to the header file. If it's in the cpp, you'll get an unresolved external error (potentially). It is however worth noting that MSVC will ignore inline in this context, given that you have asked to export the entire class (export overrules inline).

It is possible to export individual member functions as well, if you want to mix & match inline/exported.

class Foo {
public:
   LIB_MEMBER Foo ();
   OBJ_INLINE void Bar () 
     {}
};

4 Comments

Sorry, the macros I was using were debug macros because of the issue. I have deleted them.
The problem is the Microsoft documentation says you can do it.
Yes, you can mix inline & exported (if you export dividual member functions), but if you export the entire class, inline is overruled and all methods will be exported. That's what exporting a class means.
And, if you stick inline methods in the cpp file, well you are simply asking for a linker error by design. Put the method in the header file.
0

Edit: I believe this answer is wrong. Preserved for the comments below.

It is recommended to read the entire documentation page before jumping to coding.

These rules apply to inline functions whose definitions appear within a class definition.

Your function definition does not appear within a class definition, only the declaration does.

6 Comments

I found the answer and it's not what you would think. All C++ class members are actually inline. MIND BLOWN!!! I'm a C++ ninja and this was something I sluffed off. CodeGuru says "Any function defined within the body of a class automatically becomes an inline function. However, if we want a non-class function to be inline, we can do it by using the keyword inline. We must define an inline function as soon as we declare it; otherwise, it will be treated as a normal function declaration."
@CaleMcCollough "All C++ class members are actually inline" this is incorrect and this is not what the source you quoted is saying.
This is subtle, in section 9.3(3) of the C++14 standard reads "There shall be at most one definition of a non-inline member function in a program; no diagnostic is required. There may be more than one inline member function definition in a program." It's not saying that it will inline your member, but it's saying that only one exists, and the compiler then implies to use the one that it finds fastest. Given it's a DLL, there is only copy of the inline member so the compiler will reduce the ROM ssize with the optimizer while linking.
Now the paper about removing inline from the C++ standard makes sense. When you declare a function inline in C++, it basically means that there is more than one copy of that function so the compiler needs to search through them for the best one. In the case the DLL, this construct doesn't do anything because the compiler put a copy of it in the DLL. At that point, you're at the mercy of the optimizer that is looking to inline whatever it can whether you marked it inline or not.
I've changed the question slightly and half-taken out the DLL part.
|
-1

foo.cpp won't compile because of the C++ standard (C++14 in this case) Section 9.3 states:

  1. An inline member function (whether static or non-static) may also be defined outside of its class definition provided either its declaration in the class definition or its definition outside of the class definition declares the function as inline. [ Note: Member functions of a class in namespace scope have external linkage. Member functions of a local class (9.8) have no linkage. See 3.5. —end note ]

  2. There shall be at most one definition of a non-inline member function in a program; no diagnostic is required. There may be more than one inline member function definition in a program. See 3.2 and 7.1.2.

Also Section 3.4 also states:

An inline function shall be defined in every translation unit in which it is odr-used. Exactly one definition of a class is required in a translation unit if the class is used in a way that requires the class type to be complete.

The keyword to understand here is external linkage; If there is more than one definition of the symbol at link time then it can't be resolved because of the one definition rule1; all C++ class members defined in namespace scope have external linkage and there are multiple definitions of an inline symbol, one for each translation unit, so the external symbol can't be resolved; hence the compile error Unresolved External Symbol. When we put the definition of the declaration outside of the class and mark it inline, it's no different than putting the inline code in the header. So if we move the code from foo.cpp into main.cpp, it works because there is only one TU now and no violates of the one definition rule:

#include "foo.h"

Foo::Foo() {}

inline int
Foo::Bar() {  //< No undefined external refrence error.
  return 1;
}

int main(int argv, char** args) {
  Foo foo;
  std::cout << "Hello StackOverflow. foo is " << foo.Bar();
  while (1)
    ;
}

The error in logic of using inline in the declarations for the statically linked library is that we assume that the compiler will not inline the functions automatically using the optimizer. Compilers perform Link-Time Code Generation, or Whole Program Optimisation, where the linker will delay generating the code until link-time and will optimize the intermediate code without the use of a single inline tag2. The problem with using a 3-file translation unit with a DLL is that you're linking at runtime and the intermediate code got deleted so you can't use the Link-time Code Generation to inline the function, so you'll have to put inline functions in the "code header" with the templates.

Updated 9/14/2019 Answer

I have found a significantly more eloquent solution, and you can see an example in the Script2 SDK. The solution is that you only really want one translation unit. I just got 900KB of Script2 code to compile in about 4 seconds total, with 3 seconds spent on pch.cc. The trick is to rename your .cpp files to .inl files, which are inline header files, and include then all in another inline header, Script2 using the module_assembly.inl file. This will prevent you from having to pre-compile a statically linked library and lose the intermediate object code you need for the compiler to auto-inline your functions during the Whole program optimization phase. The new naming convention is that the "codeless header" is a .h file, the "code header" is a .hpp file, and the inline cpp file is .cc.inl file.

12 Comments

Your code sample should fail to compile since Bar was declared to return void
that was a typo. If the c++ class member is marked inline it says it doesn't have external linkage, so you when you mark it inline the "external reference" is undefined.
It's not appropriate to make a claim like "all of the other Stackoverflow Q&A on inline C++ class members are missing a critical piece of information". Instead you could perhaps highlight a specific answer that has a problem. Most of your last 2 paragraphs is nonsense: a function has a single address , whether inline or not. All class member functions have external linkage, inline or not. "assembly line boundary" is not standard terminology and it's not clear what you mean by that. Also "you" don't inline a function, the compiler does.
It is appropriate, most people are not aware of assembly line boundaries and their code doesn't compile. Only working in the main.cpp is not an acceptable answer. Can you please explain to me why it works in the main.cpp but not in foo.cpp?
If you clearly post the actual code you want explained I'll explain it. "assembly line boundary" is not part of the specification of C++, it seems to be some term you've made up but won't let anyone else in on what you mean by it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.