On this site 我读到了:
class MyClass;
简单地声明“有这样一个类”,它的完整定义将“稍后”(在当前文件中,在编译时,或在链接时从其他文件)
我不确定如果我在链接时理解这个过程。我写了下面的代码,应该演示它。如果我错了,请纠正我。我不确定链接时的前向声明是如何工作的。
//first.h
-----------
class Second;
class First{
public:
Second* ptr;
First();
};
//first.cpp
-----------
#include "first.h"
extern Second second;
First::First(){ptr = &second;}
//second.h
----------
class Second{
public:
Second(){};
};
//main.cpp
----------
#include "second.h"
Second second;
int main(int argc, char *argv[])
{
return 0;
}
此代码已编译。如果行第二秒;注释,链接器抛出:未定义引用'秒'。 一些评论放在一起1)前向声明2)编译单元3)链接可能会有所帮助。
答案 0 :(得分:2)
我认为您阅读的文档因其松懈而误导您:
class MyClass;
并不完全意味着有这样一个类,因为 创建类存在的唯一方法是定义它,声明是 不是定义。该声明将更好地理解为:假设有这样一个类。
而并不意味着该类的完整定义将来或将来不会出现。它' S 完整定义可能需要稍后才能成功编译。要么 不。如果完整的类定义确实需要稍后, 它需要来才能成功编译;因此在编译时,而不是链接时。
您可以激发的未定义引用链接错误
在Second second;
中评论main.cpp
只是一个问题
普通的旧的未定义的引用错误,例如你总是得到的
试图链接一个声明extern
变量的程序
在某处被引用并且无处定义。它没有必要
与extern
变量属于类类型的连接 - 相反
比如,int
- 或者与前瞻性宣言的业务有关。
类的前向声明只是必要才能抢占 编译器尝试解析定义时的死锁 两个相互依赖且无法完成的类 在完成另一个之前的类定义。
一个基本的例子:我天真地写了两个类first
和second
每个都有一个方法,使用另一个类的对象和调用
其方法之一:
<强> first.h 强>
#ifndef FIRST_H
#define FIRST_H
#include <string>
#include <iostream>
#include "second.h"
struct first {
std::string get_type() const {
return "First";
}
void use_a_second(second const & second) const {
std::cout << second.get_type() << std::endl;
}
};
#endif
<强> second.h 强>
#ifndef SECOND_H
#define SECOND_H
#include <string>
#include <iostream>
#include "first.h"
struct second {
std::string get_type() const {
return "First";
}
void use_a_first(first const & first) const {
std::cout << first.get_type() << std::endl;
}
};
#endif
<强>的main.cpp 强>
#include "first.h"
#include "second.h"
int main()
{
first f;
second s;
f.use_a_second(s);
s.use_a_first(f);
return 0;
}
尝试编译main.cpp
:
$ g++ -c -o main.o -Wall -Wextra -pedantic main.cpp
In file included from first.h:6:0,
from main.cpp:1:
second.h:13:19: error: ‘first’ has not been declared
void use_a_first(first const & first) const {
^~~~~
second.h: In member function ‘void second::use_a_first(const int&) const’:
second.h:14:22: error: request for member ‘get_type’ in ‘first’, which is of non-class type ‘const int’
std::cout << first.get_type() << std::endl;
^~~~~~~~
main.cpp: In function ‘int main()’:
main.cpp:9:8: error: expected unqualified-id before ‘.’ token
second.use_a_first(first);
编译器受到阻碍,因为first.h
包含second.h
,和
反之亦然,因此它无法获得first
之前的定义
获得second
的定义,这需要定义first
...
而反之亦然。
在定义之前的每个类的转发声明 另一个,以及每个类的相应重构 一个定义和一个实现,让我们摆脱这种致命的拥抱:
first.h(已修复)
#ifndef FIRST_H
#define FIRST_H
#include <string>
struct second; // Declaration
struct first{
std::string get_type() const {
return "first";
}
void use_a_second(second const & second) const;
};
#endif
second.h(已修复)
#ifndef SECOND_H
#define SECOND_H
#include <string>
struct first; //Declaration
struct second{
std::string get_type() const {
return "second";
}
void use_a_first(first const & first) const;
};
#endif
first.cpp(新)
#include <iostream>
#include "first.h"
#include "second.h"
void first::use_a_second(second const & second) const {
std::cout << second.get_type() << std::endl;
}
second.cpp(新)
#include <iostream>
#include "first.h"
#include "second.h"
void second::use_a_first(first const & first) const {
std::cout << first.get_type() << std::endl;
}
编译:
$ g++ -c -o first.o -Wall -Wextra -pedantic first.cpp
$ g++ -c -o second.o -Wall -Wextra -pedantic second.cpp
$ g++ -c -o main.o -Wall -Wextra -pedantic main.cpp
链接:
$ g++ -o prog main.o first.o second.o
执行命令
$ ./prog
second
first
这是前向类声明的唯一场景 需要。 可以在更广泛的情况下使用:请参阅When can I use a forward declaration?。只需要每一个需求 成功的编译,而不是链接。直到可以尝试联系 编译成功。
使用定义一词时,文档片段也是误导性的。该 类的定义在编译的上下文中意味着一件事 为了清楚起见,应该意味着什么。这意味着别的东西,松散地, 在联系的背景下,不应该意味着为了清晰起见。 在链接的背景下,我们最好只讨论实现 一个阶级 - 甚至这是一个需要资格的概念。
就编译器而言,一个类是定义的,如果它来自 开始到结束:
class foo ... {
...
};
没有错误,然后类定义是该范围的内容。一个完整的定义 当然,这并不意味着一个类有一个完整的实现。它 除了完整的定义之外,只有所有方法和 在其定义中声明的静态成员本身也在定义,或者 在类定义中内联;包含翻译中的外包 单位,或在其他翻译单位(可能在外部编译) 库)包含已编译的包含翻译单元的链接。 如果没有以这些方式之一提供任何这些成员定义 来链接时,将导致未解决的引用链接错误。那 是实现类的缺陷。
链接器对定义的想法与C ++不同 编译器和更基础的。从链接器的角度来看, 一个C ++类实际上并不存在。对于链接器,类实现由编译器简化, 一堆符号和符号定义与它得到的基本不同 从任何语言编译器,无论语言是否处理类。 对于成功而言,对链接器而言,重要的是输出二进制文件中引用的所有符号 在同一个二进制文件或请求的动态库中有 definitions 在联系中。符号(广义地)可以标识一些可执行代码或一些数据。 对于代码符号, definition 表示链接器的实现:定义是代表的代码,如果有的话。 对于数据符号, definition 表示链接器的值:它表示所表示的数据(如果有)。
所以当片段说:
..它的完整定义将是&#34;稍后出现&#34; (在当前文件中,在编译时,或在链接时从某个其他文件)
这需要分开。
类foo
的完整定义必须稍后在编译中
翻译单元,在类型foo
之前需要作为任何 else 的类型,
具体而言,基类的类型,或函数/方法参数,或对象 1 。
如果不满足此要求,将导致 compile 错误: -
如果foo
以后从未需要作为基类,参数或对象的类型,
然后,类foo
的定义永远不需要遵循声明。
类foo
的完整实现可能需要<或em ,或者
通过联系提供。由于链接器不了解类,
它并不知道不完整类的完整实现之间的区别。
您可以通过添加一个没有实现的方法来更改上面的类first
:
struct first{
std::string get_type() const {
return "first";
}
void use_a_second(second const & second) const;
void unused();
};
程序将编译,链接和运行相同。自从
编译器没有发出void first::unused()
的定义,因为
程序不会尝试调用void first::unused()
任何类型为first
的对象,或使用其地址,不提及
void first::unused()
出现在联系中。如果
我们将main.cpp
更改为:
#include "first.h"
#include "second.h"
int main()
{
first f;
second s;
f.use_a_second(s);
s.use_a_first(f);
f.unused();
return 0;
}
然后链接器会在void first::unused()
中找到main.o
的来电
当然还有一个未解决的参考错误。但这只是
意味着链接无法提供实现
计划需求。它并不意味着类定义
first
不完整。如果是的话,编译main.cpp
会有
失败了,没有尝试过联系。
Takeway: -
转发类声明可以避免编译时死锁 相互依赖的类定义,以及相应的重构。
前向类声明可以避免未解析的引用链接
错误。这样的错误总是意味着执行
程序需要代码符号或数据符号的值
而不是由联系提供。类声明也不能添加
其中一个联系方式。它没有增加链接。它
只是指示编译器在上下文中容忍foo
foo
成为类名的必要和充分的地方。
Linkage无法在链接时提供类定义的任何部分 如果在前向类声明之后,类定义变为 必需的,因为在编译时或者需要完整的类定义 一点也不。 Linkage根本不能提供类定义的一部分; 只有类实现的元素。
<小时/> [1]要明确:
class foo;
foo & bar();
...
foo * pfoo;
...
foo & rfoo = bar();
只能通过class foo
的声明进行编译,因为两者都没有
foo * pfoo
或foo & rfoo
要求存在foo
类型的对象:
指向foo的指针或引用foo不是foo
,
可是:
class foo;
...
foo f; // Error
...
foo * pfoo;
...
pfoo->method(); // Error
无法编译,因为f
必须是foo
,而是 pfoo
所解决的对象
必须存在,因此如果通过该方法调用任何方法,则为foo
对象