2

I am under the impression that you are allowed to define member functions of a class in one file and then use those functions in another file, as long as both files are compiled and sent to the linker. However, doing this gives me an undefined reference error if I use g++ (4.6.4). Interestingly, using the intel compiler (icpc 11.0) does not give an error and everything works. Is there some flag I can set in g++ to make this work, or is the intel compiler letting me get away with something I shouldn't be doing? Here is some code that reproduces my problem:

class.h:

#ifndef _H
#define _H

typedef class
{
    public:
            int a;
            int b;

            void set(int x, int y);
            int add(void);
} Test;

#endif

class.cpp:

#include "class.h"

void Test::set(int x, int y)
{
    a = x;
    b = y;
}

int Test::add(void)
{
    return a+b;
}

main.cpp:

#include <cstdio>
#include "class.h"

int main(void)
{
    Test n;
    n.set(3, 4);
    printf("%d\n", n.add());

    return 0;
}

To compile, I do:

$ g++ class.cpp main.cpp -o test 
/tmp/ccRxOI40.o: In function `main':
main.cpp:(.text+0x1a): undefined reference to `Test::set(int, int)'
main.cpp:(.text+0x26): undefined reference to `Test::add()'
collect2: ld returned 1 exit status
hewy
  • 275
  • 3
  • 8
  • 5
    No need for the `typedef`, just do `class Test {};`. – Captain Obvlious Aug 08 '14 at 21:53
  • 2
    `_H` is reserved for the implementation, chose a [legal identifier.](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier/228797#228797) – Baum mit Augen Aug 08 '14 at 21:53
  • @dyp The extra flags for g++ didn't make a difference (but I had to change `-std=c++11` to `-std=c++0x`). I think that what I did with `g++ class.cpp main.cpp` was fine, but just in case I tried `g++ -c class.cpp -o class.o` and `g++ -c main.cpp -o main.o` and finally `g++ class.o main.o -o test` and got the same error – hewy Aug 08 '14 at 22:08
  • Oops, I misread. I'm sorry. The extra flags `-Wall -Wextra -pedantic` tell the compiler to produce more warnings, which can reveal some bugs. The `-std=...` is to be more portable, the default is `gnu++98` IIRC, some extension to C++98. – dyp Aug 08 '14 at 22:24

1 Answers1

5

Okay, this is strange, but what happened is that this construct:

typedef class
{
    public:
            int a;
            int b;

            void set(int x, int y);
            int add(void);
} Test;

while legal is not being treated semantically the same by the compiler as:

class Test
{
    public:
            int a;
            int b;

            void set(int x, int y);
            int add(void);
};

The typedef version makes your methods static to the file, as indicated in the nm output:

$ nm class.o
0000000000000024 t _ZN4Test3addEv
0000000000000000 t _ZN4Test3setEii
                 U __gxx_personality_v0

While the class Test version makes them proper methods:

$ nm class2.o
0000000000000024 T _ZN4Test3addEv
0000000000000000 T _ZN4Test3setEii
                 U __gxx_personality_v0

This is why the linker failed to find the symbols.

Edit: As to why this is happening, it seems to be due to an issue with interpreting how the Standard specifies the treatment of the typedef name as a class-name. Newer compilers do not seem to exhibit the same issue. The problem reported in this question was reproduced with g++ 4.4.7.

If you move the code in your class.cpp file into main.cpp and only compile main.cpp, things will work. Alternatively, you can inline the method definitions into class.h.

If you want to leave them as separate translation units, you need to change the class.h file so that your class is defined using the class Test way instead of using the typedef on the anonymous class.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • Thanks, that worked, do you know why this happened? I thought the two versions would be equivalent. – hewy Aug 08 '14 at 22:14
  • Because you declared an anonymous class. Since the class doesn't really have a name, the C++ compiler figures it would be a violation to try to resolve a global symbol related to a method for that class. So, it made your methods file local. [Here's a related question.](http://stackoverflow.com/q/612328/315052). – jxh Aug 08 '14 at 22:20
  • 1
    The name in introduced by `typedef` is **a** *class-name**, while the Standard requires each member functions defined outside the class to use "its *class-name*" (singular). Not "one of its class-names" (plural), which would include ones created by `typedef`. Seems like the Standard could use some clarification on that. – Ben Voigt Aug 08 '14 at 22:27
  • @BenVoigt: Thanks, so I should change the "not kosher" wording to something indicating the proper way to deal with it is ambiguous? – jxh Aug 08 '14 at 22:32
  • 1
    But what about 7.1.3/9? "If the typedef declaration defines an unnamed class (or enum), the first _typedef-name_ declared by the declaration to be that class type (or enum type) is used to denote the class type (or enum type) for linkage purposes only (3.5)." And 3.5 specifically says that such classes and their members have external linkage. – aschepler Aug 08 '14 at 22:32
  • @aschepler: So you think this is a GCC bug? – jxh Aug 08 '14 at 22:34
  • If this is a g++ bug, then it's fixed in g++4.10 Because the OP's example works in my g++4.10, no linker error. – dyp Aug 08 '14 at 22:36
  • clang does not seem to have a problem with the `typedef` on anonymous struct either. – jxh Aug 08 '14 at 22:39