C++标准,是否允许两个互不可见的类重名?

C++语言 码拜 10年前 (2015-05-11) 958次浏览 0个评论
 

以下是一个测试程序,编译器是 VS2010

test1.cpp:

class Cs
{
public:
    int test()
    {
        return 1;
    }
    int test1()
    {
        return 10;
    }
};

int test1()
{
    return Cs().test() + Cs().test1();
}

test2.cpp:

class Cs
{
public:
    int test()
    {
        return 2;
    }
    int test2()
    {
        return 20;
    }
};

int test2()
{
    return Cs().test() + Cs().test2();
}

main.cpp:

#include <stdio.h>
#include <stdlib.h>

int test1();
int test2();

int main()
{
    printf("%d %d\n", test1(), test2());
    system("pause");
    return 0;
}

输出结果竟然是的:11 21

疑问:

1、同一个编译模块中,C++标准是否允许两个类重名,或者这个根本不是标准的一部分?
2、出现这样的情况是不是MS链接器的BUG?
3、如果C++标准允许类重名,链接器也没有问题,那这算不算是C++标准的一个缺陷?

引用楼主  的回复:

……
输出结果竟然是的:11 21
……

为什么不是 11 22 ?

1分
实际上,C++标准或者说大概所有的语言标准,都只规定了“如果你这么写,那么我可以保证得到什么结果”
正确的行为是有限的,错误的行为是无限的,C++标准可以明确规定其中一些行为是不允许的,但更多的错误行为不可能被全部包罗进来,甚至有些行为标准委员会可能都想不到。这类错误行为,我们称为“未定义”
未定义行为会得到什么样的结果,取决于具体处理这段代码的编译器,也许你用MS的编译器会得到某种结果或者报错,用G++又会得到另一种结果或者报错,诸如此类

通过编译器的编译不代表就是正确代码,C++标准没规定不可以写不代表就可以写

如果上面这些话楼主都不爱听,觉得是废话,那就说点也许有用的
别为难编译器,你敢为难编译器,编译器就敢玩死你
编译器只负责把你的代码转换成机器命令,如果你们之间发生了矛盾,到底是谁倒霉?我想比尔盖茨不会在意你的老板对你编写的程序发火拍桌子甚至找人事部。
只写你确定你知道会得到什么结果的代码

好吧,我承认我完全不熟悉C++标准原文
楼主提出的这类问题,我向来是按照上面的原则回避掉的
等高人回答吧,记得这里有几个标准党,是我等小白的福音

1分
引用 1 楼  的回复:

为什么不是 11 22 ?

大概,编译器对Cs().test()、Cs().test1()、Cs().test2()三个函数分别进行了编译
而Cs().test()在test1.cpp里被编译过一次后,test2.cpp里的那个就被无视了

引用 2 楼  的回复:

实际上,C++标准或者说大概所有的语言标准,都只规定了“如果你这么写,那么我可以保证得到什么结果”
正确的行为是有限的,错误的行为是无限的,C++标准可以明确规定其中一些行为是不允许的,但更多的错误行为不可能被全部包罗进来,甚至有些行为标准委员会可能都想不到。这类错误行为,我们称为“未定义”
未定义行为会得到什么样的结果,取决于具体处理这段代码的编译器,也许你用MS的编译器会得到某种结果或者……

并不是我有意要这么写的,只是我想到了一个可能会遇到的情况,所以写了上面的测试程序:

假如一个项目中,两个人无意中写了两个重名的类,会发生什么情况,出现未定义行为? 标准或者编译器不可能解决所有问题,但它应该尽量避免出现不必要的问题,上面这个问题是否是标准或者编译器力所能及的?

引用 4 楼  的回复:

并不是我有意要这么写的,只是我想到了一个可能会遇到的情况,所以写了上面的测试程序:

假如一个项目中,两个人无意中写了两个重名的类,会发生什么情况,出现未定义行为? 标准或者编译器不可能解决所有问题,但它应该尽量避免出现不必要的问题,上面这个问题是否是标准或者编译器力所能及的?

就我所知道的范围内,这个问题是未定义的
此外,C++的命名空间就是为了预防“无意重名”而设计的,虽然有多少效果看个人习惯或项目组编程风格

你用类都这样用的?直接使用类里面的函数?而且不写头文件?
引用 6 楼  的回复:

你用类都这样用的?直接使用类里面的函数?而且不写头文件?

测试程序,尽量简洁

Cs().test()
这是什么用法,头次见类可以这么用的….
引用 7 楼  的回复:

引用 6 楼  的回复:

你用类都这样用的?直接使用类里面的函数?而且不写头文件?

测试程序,尽量简洁

你要按照正常使用来测试,你看看能编译通过不?

引用 9 楼  的回复:

Cs().test()
这是什么用法,头次见类可以这么用的….

构造一个临时对象,然后调用其成员函数

其实这么写更好些:

Cs obj;
obj.test();

1分
test1.cpp和test2.cpp都是编译单元,并且其中的Cs互不可见,并不会对编译产生影响。所以代码产生结果的问题应该是在链接环节上,受链接器内部工作原则和细节决定,例如test1.obj和test2.obj在链接是会不会发生合并,或者根据链接顺序具有优先级?不得而知。
2分
推测是链接顺序的问题。
如果
g++ -o demo main.cpp test1.cpp test2.cpp
输出就是11, 21, Cs.test()首先在test1.cpp里找到所以Cs.test()返回1

如果
g++ -o demo main.cpp test2.cpp test1.cpp
输出就是12, 22, Cs.test()首先在test2.cpp里找到所以Cs.test()返回2

引用 13 楼  的回复:

推测是链接顺序的问题。
如果
g++ -o demo main.cpp test1.cpp test2.cpp
输出就是11, 21, Cs.test()首先在test1.cpp里找到所以Cs.test()返回1

如果
g++ -o demo main.cpp test2.cpp test1.cpp
输出就是12, 22, Cs.test()首先在test2.cpp里找到所以Cs……

是的

引用 13 楼  的回复:

推测是链接顺序的问题。
如果
g++ -o demo main.cpp test1.cpp test2.cpp
输出就是11, 21, Cs.test()首先在test1.cpp里找到所以Cs.test()返回1

如果
g++ -o demo main.cpp test2.cpp test1.cpp
输出就是12, 22, Cs.test()首先在test2.cpp里找到所以Cs……

感觉出现这种情况,应该像函数重名那样链接无法通过,或者至少给个warning

1分
第一,就这么几行代码,怎么可能出现重名问题而不能发现?
第二,如果代码量很巨大,那么在使用上就不可能像这几行代码这样这么随意,如果你把类声明都include进来,编译时就发现问题了。
第三,c++提供了命名空间。

最后,我们不要把精力花在毫无意义的假设和对这毫无意义的假设进行求证之上。

引用 16 楼  的回复:

第一,就这么几行代码,怎么可能出现重名问题而不能发现?
第二,如果代码量很巨大,那么在使用上就不可能像这几行代码这样这么随意,如果你把类声明都include进来,编译时就发现问题了。
第三,c++提供了命名空间。

最后,我们不要把精力花在毫无意义的假设和对这毫无意义的假设进行求证之上。

没那么简单,请用心思考

2分
我给楼主提供一条思路。因为如果以楼主这样在test1.cpp与test2.cpp2个CPP文件里面定义不加任何修饰的text()函数,会使test1.cpp里面的text()函数在链接时覆盖掉test2.cpp里面的text(),于是就得出楼主的结果。你可以声明text()为虚函数(virtual text())这样有可能会得到楼主想要的结果。
//都声明为虚函数  结果是12 22.
//2个text函数只声明一个为虚函数 结果为 11 22.我想这个应该是楼主想要的答案。这样两个text函数就互不干扰了。
//都不声明为虚函数 结果就是楼主的结果。
代码如下:
text1.cpp

class Cs
{
public:
     int test()//其实这里也可以加virtual,但2个CPP文件里只能选择一个加virtual
    {
        return 1;
    }
    int test1()
    {
        return 10;
    }
};

int test1()
{
    return Cs().test() + Cs().test1();
}

text2.cpp

class Cs
{
public:
     virtual int test()//这里加了个virtual
    {
        return 2;
    }
    int test2()
    {
        return 20;
    }
};

int test2()
{
    return Cs().test() + Cs().test2();
}

main.cpp

#include <stdio.h>
#include <stdlib.h>
int test1();
int test2();
int main()
{
    printf("%d %d\n", test1(), test2());
    system("pause");
    return 0;
}
引用 18 楼  的回复:

我给楼主提供一条思路。因为如果以楼主这样在test1.cpp与test2.cpp2个CPP文件里面定义不加任何修饰的text()函数,会使test1.cpp里面的text()函数在链接时覆盖掉test2.cpp里面的text(),于是就得出楼主的结果。你可以声明text()为虚函数(virtual text())这样有可能会得到楼主想要的结果。
//都声明为虚函数  结果是12 22.
//……

因为加了 virtual 后函数符号改变了,相当于函数名不同就和 test1 和 test2 没有链接问题一样

引用 20 楼  的回复:

引用 18 楼 的回复:

我给楼主提供一条思路。因为如果以楼主这样在test1.cpp与test2.cpp2个CPP文件里面定义不加任何修饰的text()函数,会使test1.cpp里面的text()函数在链接时覆盖掉test2.cpp里面的text(),于是就得出楼主的结果。你可以声明text()为虚函数(virtual text())这样有可能会得到楼主想要的结果。
//都声明为虚函数……

不知道楼主学没学过虚函数? 对于同样一个函数,加了virtual就可以实现多态了。就是同一个函数多种状态。楼主的意思是加了virtual那两个text是完全不一样的函数?

引用 21 楼  的回复:

引用 20 楼  的回复:
引用 18 楼 的回复:

我给楼主提供一条思路。因为如果以楼主这样在test1.cpp与test2.cpp2个CPP文件里面定义不加任何修饰的text()函数,会使test1.cpp里面的text()函数在链接时覆盖掉test2.cpp里面的text(),于是就得出楼主的结果。你可以声明text()为虚函数(virtual text())这样有可能会得到楼主想……

这个跟链接器的实现细节有关,将 test 的实现注释,并查看链接错误:

没加virtual时,两个test符号相同

1>test1.obj : error LNK2019: 无法解析的外部符号 “public: int __thiscall Cs::test(void)” (?test@Cs@@QAEHXZ),该符号在函数 “int __cdecl test1(void)” (?test1@@YAHXZ) 中被引用
1>test2.obj : error LNK2001: 无法解析的外部符号 “public: int __thiscall Cs::test(void)” (?test@Cs@@QAEHXZ)

其中一个加了virtual后,可以看出链接符号产生了差异

1>test1.obj : error LNK2001: 无法解析的外部符号 “public: virtual int __thiscall Cs::test(void)” (?test@Cs@@UAEHXZ)
1>test2.obj : error LNK2019: 无法解析的外部符号 “public: int __thiscall Cs::test(void)” (?test@Cs@@QAEHXZ),该符号在函数 “int __cdecl test2(void)” (?test2@@YAHXZ) 中被引用

1分
这样的问题我觉得在事先就该规划好,防止隐患吧
引用 22 楼  的回复:

引用 21 楼 的回复:

引用 20 楼 的回复:
引用 18 楼 的回复:

我给楼主提供一条思路。因为如果以楼主这样在test1.cpp与test2.cpp2个CPP文件里面定义不加任何修饰的text()函数,会使test1.cpp里面的text()函数在链接时覆盖掉test2.cpp里面的text(),于是就得出楼主的结果。你可以声明text()为虚函数(virtual text……

 无法解析的外部符号。这是你的text声明了没有定义的结果。
还有就是你最好查查virtual的用法。虚函数这一章还是比较重要的一块,它是实现运行时函数多态性的关键。还有从 printf(“%d %d\n”, test1(), test2());你是学了C语言正在学习C++的童鞋,C++的三大特点是多态性 封装性 继承性 。这个是区别于C的重要特性哦。

引用 24 楼  的回复:

引用 22 楼  的回复:
引用 21 楼 的回复:

引用 20 楼 的回复:
引用 18 楼 的回复:

我给楼主提供一条思路。因为如果以楼主这样在test1.cpp与test2.cpp2个CPP文件里面定义不加任何修饰的text()函数,会使test1.cpp里面的text()函数在链接时覆盖掉test2.cpp里面的text(),于是就得出楼主的结果。你可以声明text()为……

谢谢,^_^

那是好几年前的事情了

补充下说明。对于C++重要特点多态性。书上是这样解释的,为了写N个功能相似函数,C语言的做法是写函数名不同的函数以便实现不同的功能,而C++把功能相似的函数用同一个函数名让其代码看起来更好的表达其中的含义,这样C++就添加了多态这个在C里完全没有的概念。如果楼主不会使用虚函数,不会用重载,那么楼主还是停留在C语言的概念而没有真正认识到C++的特性。
最后总结一下。不管是重载函数还是虚函数,本来函数可以不同,但是功能相似,那么在C++里面让其代码更易懂,就用同一个函数名实现不同功能。
14分
在类声明中写实现的话则 函数就是inline的函数 ,而inline函数也会存在当前编译单元符号表中。

编译器会保证 同样函数签名的inline函数不会链接冲突。

如果有两个不一样实现的inline函数,那么编译器就就自己决定用哪个实现,所以inline函数都会写在头文件中保证在每个编译单元中实现一致。

14分
引用楼主  的回复:

1、同一个编译模块中,C++标准是否允许两个类重名,或者这个根本不是标准的一部分?
2、出现这样的情况是不是MS链接器的BUG?
3、如果C++标准允许类重名,链接器也没有问题,那这算不算是C++标准的一个缺陷?

1.不允许。不过主楼的程序涉及三个不同的 translation units,所以和同一个 translation unit 是否允许重名没有关系。标准相关的定义叫 one definition rule (ODR),c++11 3.2/5
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), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) 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 …

主楼程序中的 test 函数在 test1.cpp 和 test2.cpp 中的 sequence of tokens 是不一样的,因为一个是 return 1; 另一个是 return 2;
因此,主楼的程序违背了 ODR 原则,属于 C++ 经典的 undefined behavior.

2.这里不存在 bug 可言。因为所谓的 bug 是说程序的行为与正确的行为不一致,但是 c++ 标准说主楼程序的行为是 undefined,因此没有所谓正确的行为,因此也没有 bug 可言。

3.显然不是 c++ 的缺陷。c++ 对此有清晰的定义,叫做 undefined behavior。简而言之,就是不要写这样的程序,属于标准认证的自找麻烦的程序。

引用 27 楼  的回复:

在类声明中写实现的话则 函数就是inline的函数 ,而inline函数也会存在当前编译单元符号表中。

编译器会保证 同样函数签名的inline函数不会链接冲突。

如果有两个不一样实现的inline函数,那么编译器就就自己决定用哪个实现,所以inline函数都会写在头文件中保证在每个编译单元中实现一致。

inline 是对编译器的一个建议,而非面向类的使用者;用户并不需要关注使用的函数是不是 inline 的

这个问题跟是否 inline 无关;虽然函数写在头文件中,编译时不照样是 #include 到 cpp 中吗?

引用 28 楼  的回复:

引用楼主  的回复:
1、同一个编译模块中,C++标准是否允许两个类重名,或者这个根本不是标准的一部分?
2、出现这样的情况是不是MS链接器的BUG?
3、如果C++标准允许类重名,链接器也没有问题,那这算不算是C++标准的一个缺陷?

1.不允许。不过主楼的程序涉及三个不同的 translation units,所以和同一个 translation unit 是否允许重名没有关系。标……

1、谢谢

2、说是bug不恰当,可不可以认为是设计不周全的地方?

3、

引用 4 楼  的回复:

并不是我有意要这么写的,只是我想到了一个可能会遇到的情况,所以写了上面的测试程序:

假如一个项目中,两个人无意中写了两个重名的类,会发生什么情况,出现未定义行为? 标准或者编译器不可能解决所有问题,但它应该尽量避免出现不必要的问题,上面这个问题是否是标准或者编译器力所能及的?

引用 30 楼  的回复:

引用 28 楼  的回复:
1、谢谢

2、说是bug不恰当,可不可以认为是设计不周全的地方?

3、
引用 4 楼 的回复:
并不是我有意要这么写的,只是我想到了一个可能会遇到的情况,所以写了上面的测试程序:

2.可以吧。只不过要设计周全了,可能会很困难。

假如一个项目中,两个人无意中写了两个重名的类,会发生什么情况,出现未定义行为?

是的。

标准或者编译器不可能解决所有问题,但它应该尽量避免出现不必要的问题,上面这个问题是否是标准或者编译器力所能及的?

标准没有按你希望的方式提供解决方案,不过标准有提供 namespace,此项设计恰恰就是为了解决名字冲突的问题。这也是为什么多人合作的大工程,都建议使用 namespace 的原因。实际上,我认为比起指定繁琐的规定去理清当重复定义出现时怎样解决,namespace 是更合理的解决方案。显然标准还是很成熟的。

1分
编译器在执行某方法时会跳到方法内部,然后再return到原方法中,
由于是非static 方法,所以是在用到时才编译的方法,
你的类名,方法名相同,编译器会认为是同样一个东西, 所以不再重复编译同名方法,
其实你第二个CPP中,调用了第一个类里面的test()方法。
调试结果确实是这样。。。
1分
这涉及到编译的时候common块问题,你可以搜索一下。
引用 29 楼  的回复:

引用 27 楼  的回复:

在类声明中写实现的话则 函数就是inline的函数 ,而inline函数也会存在当前编译单元符号表中。

编译器会保证 同样函数签名的inline函数不会链接冲突。

如果有两个不一样实现的inline函数,那么编译器就就自己决定用哪个实现,所以inline函数都会写在头文件中保证在每个编译单元中实现一致。

inline 是对编译器的一个建议,……

inline是建议,但写在头文件中的函数只要加了inline这个关键字,在被多个文件包含的时候就不会有链接冲突,不管该函数是否被内联。

引用 34 楼  的回复:

inline是建议,但写在头文件中的函数只要加了inline这个关键字,在被多个文件包含的时候就不会有链接冲突,不管该函数是否被内联。

是的,不过这跟我提的问题好像没有关系

1分
引用 32 楼  的回复:

编译器在执行某方法时会跳到方法内部,然后再return到原方法中,
由于是非static 方法,所以是在用到时才编译的方法,
你的类名,方法名相同,编译器会认为是同样一个东西, 所以不再重复编译同名方法,
其实你第二个CPP中,调用了第一个类里面的test()方法。
调试结果确实是这样。。。

这是局限于 vs 的片面结论,比如我用 g++-4.7.0 编译,输出结果就是 11 22。标准说的是 undefined behavior,意思就是编译器自己看着办,因此在这个程序上 vs 和 g++ 可以说都是正确的。只不过这种程序基本是无用的,因为其行为没有确定性。

不要再纠结于如何理解看似高深莫测的 undefined behavior,因为这样的行为不受 c++ 标准指导,获得的”知识”也没有广泛的通用性,不同编译器可能不一样,同款编译器不同版本也可能不一样。
引用 35 楼  的回复:

引用 34 楼  的回复:
inline是建议,但写在头文件中的函数只要加了inline这个关键字,在被多个文件包含的时候就不会有链接冲突,不管该函数是否被内联。

是的,不过这跟我提的问题好像没有关系

当然有关系了,写在类里面的默认是inline函数。
那么相同函数签名的inline函数就不会有链接错误,编译器自己选择了其中一个,你试试release版看看。

再把实现都写在外面在编译试试看

请LZ描述下编译链接的过程。
引用 27 楼  的回复:

在类声明中写实现的话则 函数就是inline的函数 ,而inline函数也会存在当前编译单元符号表中。

编译器会保证 同样函数签名的inline函数不会链接冲突。

如果有两个不一样实现的inline函数,那么编译器就就自己决定用哪个实现,所以inline函数都会写在头文件中保证在每个编译单元中实现一致。

确实是这样,谢谢

[Quote=引用 40 楼  的回复:
在类声明中写实现的话则 函数就是inline的函数 ,而inline函数也会存在当前编译单元符号表中。

编译器会保证 同样函数签名的inline函数不会链接冲突。

如果有两个不一样实现的inline函数,那么编译器就就自己决定用哪个实现,所以inline函数都会写在头文件中保证在每个编译单元中实现一致。
[/Quote]

所以问题是,当有两个或以上相同签名的 inline 函数时,最终被链接的那个函数,不一定是 #include 的那个。

引用 34 楼  的回复:

当然有关系了,写在类里面的默认是inline函数。
那么相同函数签名的inline函数就不会有链接错误,编译器自己选择了其中一个,你试试release版看看。

再把实现都写在外面在编译试试看

release一样的结果,写在外面出现链接错误

结论是当出现两个签名相同实现不同的inline函数时,实际被调用的是哪个由编译器决定,而不一定是#include的那个。

thankyourepay
不怎么用csdn,接分是神马?
引用 16 楼  的回复:

第一,就这么几行代码,怎么可能出现重名问题而不能发现?
第二,如果代码量很巨大,那么在使用上就不可能像这几行代码这样这么随意,如果你把类声明都include进来,编译时就发现问题了。
第三,c++提供了命名空间。

最后,我们不要把精力花在毫无意义的假设和对这毫无意义的假设进行求证之上。

LZ只是打个比方 上百万行代码的工程里 如果某些参与者没有很好的用命名空间包起自己的代码段 出现这种问题不新鲜吧 发现问题去求证一下不是个好事情么??为什么叫无意义呢
使用规范化是要求也只能是要求 没办法阻止一些2把刀做出这种事情来吧
既然没办法用暴力手段 比如编译错误 这种方式来阻止 我们是不是有必要探寻一下深层次的原因并且给出人为的手段来想办法在第一时间发现类似的问题 而不是等到运行时候出现错误的结果然后在大量的代码和Obj里面大海捞针呢?
请这位说没必要的仁兄回答一下

部分编译问题。也可以说是编译器缺陷。
正如楼上不少人所说的那样,C++不允许同一环境下的类重名,但在不同的命名空间,是可以有重名类的。
楼主的问题在于,两个不同的文件编译各自的全局函数,然后又由main组合输出,这在部分编译中,是允许这么做得,楼主自己概念有些乱。

C++允许不同环境下的中间编译,进而混合编程,而楼主却试图同样环境下的调度,分布在不同文件下私有静态类却由两个静态私有全局函数调用,而在main函数未申明extern全局函数情况下却又能够调用未定义为外部全局的函数,编译器在此问题上约束性不够严格。否则应该出现11、22的结果。

建议更换编译器试试看

另外,放弃VS系列的软件工具吧,除非比不做产品,不然的话,你会被这种东西拖累死。我没听说过微软的那款产品出自vs系列,vs出了便宜以外,微软似乎没把其作为商业开发软件处理的
收藏下~~~~
楼主你好,书上说 “对于同一程序的不同文件,如果inline同名函数出现的话,其定义必须相同,否则会出现不定的情况”。
这个不定的情况是指,对于同名的内联函数,链接期,先碰到哪个.o文件,就会只用那个.o文件中的inline函数定义。
由于先碰到哪个.o文件是不定的,所以说输出结果是不定的。
下面我做了一张图,十分清晰地展示了我上面的话。
C++标准,是否允许两个互不可见的类重名?
又试了下,如果开了编译器优化的话,即使只用了-O1优化,也可以正常输出了
因为一开了优化,.o文件中就没有内联函数符号了。
第一张图是没有开优化的时候:
C++标准,是否允许两个互不可见的类重名?

下面这张图片是开过优化之后
C++标准,是否允许两个互不可见的类重名?

开过优化之后,输出就正常了。
C++标准,是否允许两个互不可见的类重名?

不过,分析了这么多,还是那么句话:大型项目请使用namespace,还有,同一命名空间下,如果inline同名函数出现的话,其定义必须相同,否则会出现不定的情况。


CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明C++标准,是否允许两个互不可见的类重名?
喜欢 (0)
[1034331897@qq.com]
分享 (0)

文章评论已关闭!