为什么在Effective C++中声明和定义是这样定义的
Why is declaration and definition defined this way in Effective C++?
在有效C++(第三版)第2项(首选const
、enum
和inline
而非#define
)中,类特定常量的代码段读取:
class GamePlayer {
private:
static const int NumTurns = 5; // constant declaration
int scores[NumTurns]; // use of constant
...
};
然后这本书说(用我自己的话来说)static const int NumTurns = 5;
不是一个定义,这通常是C++对类成员所要求的,除非它是一个从未使用过地址的静态积分常数。如果常量的上述情况不正确,或者编译器出于任何原因坚持定义,则应在实现文件中提供如下定义:
const int GamePlayer::NumTurns; // definition of NumTurns; see
// below for why no value is given
根据这本书(也用我自己的话来说),定义中没有给出任何值,因为它已经在声明中给出了。
这混淆了我认为我已经知道的关于声明和定义的定义(在问这个问题之前,我在谷歌上仔细检查了一下):
- 为什么
static const int NumTurns = 5
不是一个定义?这里NumTurns
不是初始化为5
的值吗?当一个声明和一个定义一起发生时,它不是被称为初始化吗 - 为什么
static
积分常数不需要定义 - 为什么在没有定义值的情况下,第二个代码片段被认为是一个定义,而包含该值的类内部的声明仍然不是一个(本质上要回到我的第一个问题)
- 初始化不是一个定义吗?为什么这里没有违反"唯一定义"规则
在这一点上,我可能只是混淆了自己,所以有人可以从头开始重新教育我:为什么这两行代码声明和定义而不是另一行,那里有初始化的例子吗?初始化也是一个定义吗?
来源:代码片段直接从书中引用。
编辑:额外参考定义和声明之间的区别是什么?
- 声明引入标识符和类型
- 定义实例化并实现
所以,是的。。。这似乎不是这里发生的事情。
编辑2:我认为编译器可以通过不将静态积分常数存储在内存中,而只是在代码中内联替换它来优化它。但是如果使用NumTurns
地址,为什么声明不自动变为声明+定义,因为实例化已经存在了?
编辑3:(这次编辑与原始问题没有太大关系,但仍然很突出。我把它放在这里,这样我就不需要复制粘贴到下面每个答案的评论中。请在评论中回答我。谢谢!)
谢谢你的回答。我的头脑现在清楚多了,但编辑2的最后一个问题仍然存在:如果编译器检测到程序中需要定义的条件(例如程序中使用了&NumTurns
),为什么它不自动将static const int NumTurns = 5;
重新解释为声明&定义而不是仅声明?它具有程序中其他任何地方的定义所具有的所有语法。
我在学校里有Java背景,不需要像上面那样为静态成员单独定义。我知道C++是不同的,但我想知道为什么上面是这样的。如果从未使用过地址,则内联替换静态积分成员对我来说更像是一种优化,而不是一种基本特征,那么,为什么我需要绕过它(在条件不满足时提供一个单独的语句作为定义,即使原始语句的语法足够),而不是反过来(编译器在需要原始语句时将其视为定义,因为语法足够)?
免责声明:我不是一个标准的大师。
我建议你阅读以下两篇:
http://www.stroustrup.com/bs_faq2.html#in-类
和:
定义和声明之间有什么区别?
为什么static const int NumTurns=5不是一个定义?NumTurns不是在这里初始化为值5,当声明和定义一起出现,称为初始化?
在高级中,定义(与声明相反)实例化或实现实体(变量、类、函数)
如果是变量,则定义会将变量分配到程序内存中
在函数的情况下,定义给出了可以编译为汇编指令的指令。*
代码行
static const int NumTurns = 5;
默认情况下不会在程序内存中分配NumTurns
,因此它只是一个声明。为了在程序内存中创建NumTurns
的(唯一)实例,您必须提供所需的定义:
const int MyClass::NumTurns;
Bjarne报价:
如果(且仅当)静态成员具有类外定义:
class AE {
// ...
public:
static const int c6 = 7;
static const int c7 = 31;
};
const int AE::c7; // definition
int f()
{
const int* p1 = &AE::c6; // error: c6 not an lvalue
const int* p2 = &AE::c7; // ok
// ...
}
为什么静态积分常数不需要定义?
如果你想记录他们的地址,他们会这样做。
初始化不是一个定义吗?
没有。初始化是在某个实体(基元、对象)中设置初始值的行为。
void someFunc (){
int x; //x is declared, but not initialized
int y = 0; //y is declared+ initialized.
}
为什么这里没有违反"唯一定义"规则?
为什么会这样?在整个代码中仍然有一个NumTurns
。符号MyClas::NumTurns
仅出现一次。该定义仅使变量出现在程序内存中。ODR规则指定任何符号只能声明一次。
编辑:这个问题基本上可以归结为"为什么编译器不能自己决定?"我不是一个滑稽的成员,所以我不能给出完全合法的答案
我聪明的猜测是,让编译器决定事情不是C++哲学。当您让编译器决定(例如在哪里声明静态积分常量)时,I、 作为开发人员,可能会提出以下问题:
1) 如果我的代码是一个只有头的库,会发生什么?编译器应该在哪里声明变量?在它遇到的第一个cpp文件中?在包含main的文件中?
2) 如果编译器决定在哪里声明变量,我有任何保证吗该变量将与其他静态变量(我已手动声明)一起声明,从而保持缓存位置紧张?
当我们赞同"让审计官疯狂"的心态时,人们提出了越来越多的问题我认为这是Java和C++之间的根本区别。
Java:让JVM做它的hueristics
C++:让开发人员进行评测。
最终,在C++中,编译器检查所有内容是否正确,并将代码转换为二进制代码,不要代替开发人员做这项工作
*或者字节码,如果您使用托管C++(boo…)
静态积分常数不需要定义的原因是它没有必要。如果该值是在类定义中初始化的,那么无论何时使用,编译器都可以替换初始化的值。实际上,这意味着该值不需要实际占用程序中的内存位置(只要没有代码计算该常量的地址,在这种情况下需要定义)。
声明、定义和初始化实际上是单独的(尽管是相关的)概念。一个声明告诉编译器存在某种东西。定义是一种导致某个东西存在的声明类型(因此具有其他声明可见性的代码可以引用它)-例如,为它分配内存。初始化是给定值的行为。这种区别实际上发生在语言的其他部分。例如
#include <iostream>
int main()
{
int x; // declaration and definition of x
std::cout << x << 'n'; // undefined behaviour as x is uninitialised
x = 42; // since x is not yet initialised, this assignment has an effect of initialising it
std::cout << x << 'n'; // OK as x is now initialised
}
在实践中,初始化可以是声明的一部分,但不是必须的。
编辑以回应原始问题中的"编辑3":
C++有一个单独的编译模型。Java的模型依赖于C++模型所没有的功能(更智能的链接器、运行时链接)。在C++中,如果一个编译单元看到一个声明而没有定义,编译器只会假设定义在另一个编译单位中。通常(对于许多构建链),链接器稍后会检测是否不存在必要的定义,因此链接阶段失败。相反,如果每个需要定义存在的编译单元实际上都创建了一个定义,编译器就会打破"一个定义规则",而一个典型的愚蠢链接器——它还不够聪明,无法将重复定义的东西折叠成一个定义——就会抱怨一个多重定义的符号。
为什么静态常量NumTurns=5不是一个定义?NumTurns在这里没有初始化为值5吗?当一个声明和一个定义同时发生时,它不是被称为初始化吗?
这是个错误的问题。声明是指通知编译器类型/变量/函数/存在的任何东西以及它是什么。定义是指指示编译器实际分配存储以保留所述实体。
由于成员是在类的声明中"定义"的(即,此时没有创建类的实例),因此这是一个声明。
调用此定义所依赖的等号只是结构成员的默认值,而不是初始化。
为什么静态积分常数不需要定义?
你自己也回答过这个问题。这是因为编译器可以避免为它们分配任何存储,而只需将它们放入使用的代码中。
为什么在没有定义值的情况下,第二个代码片段被认为是一个定义,而包含该值的类内部的声明仍然不是一个(本质上要回到我的第一个问题)?
正如我之前所说,这是因为第二段代码为变量分配存储空间。
初始化不是一个定义吗?为什么这里没有违反"唯一定义"规则?
因为初始化不是定义。
如果编译器检测到程序中需要定义的条件(例如程序中使用了&NumTurns),为什么它不自动重新解释静态常量int NumTurns=5;作为声明&定义而不是仅声明?
因为定义分配了存储。更具体地说,因为如果编译器这样做,那么在不同的编译单元中会有多个存储分配。这很糟糕,因为在链接过程中,链接器只需要一个。链接器看到具有相同作用域的同一变量的多个定义,但不知道它具有相同的值。它所知道的是,它无法将它们全部合并到一个位置。
为了避免这个问题,它要求您手动进行定义。您可以在cpp文件中定义它(即,不在头中),从而将分配解析为链接器可以接受的一个特定文件。
- 如何确保C++函数在定义之前声明(如override关键字)
- 为什么在定义函数之前先声明它
- 为什么我不能在一个类的不同行中声明和定义成员变量?
- Visual Studio中的函数声明和函数定义问题
- C++错误C2600:无法定义编译器生成的特殊成员函数(必须首先在类中声明)
- 如何在标头中声明(或定义)函数的问题
- 程序顶部的声明与定义(最佳实践)
- 类的前向声明之后的类成员函数定义,在类声明之前
- 静态变量声明和定义
- C++ G++ 编译器 - 错误:隐式声明的定义
- C++:错误重定义和先前声明
- 类模板静态数据成员定义/声明/初始化
- 只有一个定义/声明时标头声明变量的多堆定义错误
- OpenCV - Ptr 语法和类定义/声明 - 混淆?
- 如何为非类型模板类的专用化定义声明之外的方法
- 我如何防止静态类成员变量需要两个定义/声明
- 默认定义声明的详细程度不完整
- C++:非成员函数的定义/声明的位置
- 将比较操作符的重载定义/声明为库中的非成员函数
- C++头重新定义/声明混合