Пространство имен и статических членов класса

Допустим, у меня есть два файла:

/**
 * class.cpp
 */ 
#include <stdio.h>
class foo 
{
private:
        int func();
};

int foo::func(void)
{
        printf("[%s:%d]: %s\n", __FILE__, __LINE__, __FUNCTION__);
        return -1; 
}

и

/**
 * main.cpp
 */ 
#include <stdio.h>
namespace foo 
{
        int func(void);
}
int main(void)
{
        int ret = foo::func();
        printf("[%s:%d]: ret=%d\n", __FILE__, __LINE__, ret);
        return 0;
}

скомпилирован следующим образом:

g++ -o a.out main.cpp class.cpp 

Существует вывод из исполняемого файла:

[class.cpp:15]: func
[main.cpp:14]: ret=-1

И наконец мой вопрос:

Почему этот примерный код скомпилирован без каких-либо ошибок, и мы можем вызвать метод private класса класс foo?

Скомпилирован с gcc 4.6.3, но не только. Я знаю, что компилятор не различает эти два символа (func) из пространства имен foo и частной функции foo из класса foo б > ). Выход из nm:

nm class.o
00000000 T _ZN3foo4funcEv
00000017 r _ZZN3foo4funcEvE12__FUNCTION__
         U printf

nm main.o
         U _ZN3foo4funcEv
00000000 T main
         U printf

Я хотел бы спросить, правильно ли это поведение или нет? ИМХО это неправильное поведение, и оно вообще небезопасно (разрушает инкапсуляцию).

Я хотел бы упомянуть, что компилятор из visual studio 2008 не связывает эти два символа.

Ответ 1

Почему компилятор не жалуется?

Обратите внимание, что "класс", "struct" и "namespace" все определяют пространство имен в отношении компилятора. Таким образом, компилятор соответствующим образом украшает символы. Он будет жаловаться, если вы определите как класс, так и пространство имен в том же файле, но здесь это не так.

Почему линкер не жалуется?

То, как вы написали код, оставляет func(), определенный в namespace foo слабее, чем func(), определенный в class foo. В принципе, func(), определенный в namespace foo, является просто сигнатурой без реализации. Вы можете видеть, что для компоновщика это разрешено разрешать символ во время выполнения, потому что реализация не находится в main.cpp:

nm main.o
       U _ZN3foo4funcEv
//Here^^^^

Таким образом, поскольку имена пространства имен и классов были одинаковыми (приводя к тем же символам для foo::func), компоновщик решает символ в момент ссылки, находит сильное определение с тем же символом и ссылки на него.

Если вы должны были реализовать func() в namespace foo:

/**
 * main.cpp
 */
#include <stdio.h>

namespace foo
{
    int func(void) {
        printf("NM_FOO [%s:%d]: %s\n", __FILE__, __LINE__, __FUNCTION__);
        return -1;
    };
}
int main(void)
{
    int ret = foo::func();
    printf("[%s:%d]: ret=%d\n", __FILE__, __LINE__, ret);
    return 0;
}

Вы бы увидели, что компоновщик жалуется:

duplicate symbol foo::func()     in:
/var/folders/.../class.o
/var/folders/.../main.o
ld: 1 duplicate symbol for architecture x86_64

Если вы посмотрите на main.o на этот раз, вы увидите:

0000000000000064 T __ZN3foo4funcEv
0000000000000158 S __ZN3foo4funcEv.eh
00000000000000e0 s __ZZN3foo4funcEvE12__FUNCTION__
0000000000000000 T _main
0000000000000128 S _main.eh
                 U _printf

И class.o:

0000000000000000 T __ZN3foo4funcEv
00000000000000a0 S __ZN3foo4funcEv.eh
0000000000000080 s __ZZN3foo4funcEvE12__FUNCTION__
                 U _printf

оба из них определяют одинаковый символ функции одинаково сильным, что приводит к ошибке компоновщика.

Помните, что компоновщик не знает о различии между пространством имен и классом. Он разрешает символы, которые находятся в объектном коде. Он будет жаловаться только на сильные переиделения. Одно или несколько более слабых определений с одним сильным определением прекрасно подходят в мире компоновщиков.

Ответ 2

Поскольку вы определили foo() как члена пространства имен в main.cpp, то как компилятор обработал его. Различие между классом/структурой/пространством имен public/private и т.д. Зависит от того, компилятор знает определение функции - здесь вы намеренно решили обмануть его.

Линкер не знает о таких различиях, он просто решает имена символов, а в случае вашего компилятора украшение названий функций заканчивается. То, как имена символов украшены, не указано в С++, поэтому это вполне допустимое поведение.