为什么在Effective C ++中以这种方式定义声明和定义?

时间:2016-01-06 09:42:20

标签: c++ effective-c++

Effective C ++ (第3版),第2项(首选constenuminline#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

根据这本书(也用我自己的话说),定义中没有给出任何价值,因为它已在声明中给出。

这让我认为我已经知道的声明和定义的定义令人困惑(我在问这个问题之前在Google上仔细检查过):

  • 为什么static const int NumTurns = 5不是定义?这里NumTurns未初始化为5,并且当声明和定义一起出现时,它不是称为初始化吗?
  • 为什么static积分常数不需要定义?
  • 为什么第二个代码片段在没有定义值时被认为是一个定义,但是包含该值的类中的声明仍然不是一个(基本上回到我的第一个问题)?
  • 是不是初始化定义?为什么这里没有违反“唯一定义”规则?

我现在可能只是在这里混淆自己,所以有人可以从头开始重新教育我:为什么这两行代码声明和定义而不是另一行,并且有任何初始化实例?初始化也是定义吗?

信用:代码片段直接引自本书。

修改:附加参考What is the difference between a definition and a declaration?

  • 声明引入标识符和类型
  • 定义实例化并实现

所以,是的......这似乎不是这里发生的事情。

编辑2 :我认为编译器有可能通过不将其存储在内存中来优化静态整数常量,并且只是在代码中将其替换为内联。但是如果使用NumTurns地址,为什么声明会自动变为声明+定义,因为实例化已经存在?

编辑3 : (这个编辑与原始问题关系不大,但仍然很突出。我把它放在这里,这样我就不需要复制粘贴到下面每个答案的评论中。请在评论中回答我这个问题。谢谢!)

感谢您的回答。我的头脑现在更清楚了,但编辑2中的最后一个问题仍然存在:如果编译器检测到程序中需要定义的条件(例如程序中使用了&NumTurns),为什么不呢?只需自动重新解释static const int NumTurns = 5;作为声明&定义而不是仅声明?它具有程序中任何其他位置的所有语法定义。

我来自学校的Java背景,并且不需要以上述方式为静态成员制定这样的单独定义。我知道C ++是不同的,但我想知道上面为什么会这样。如果从不使用地址,则内联替换的静态积分成员听起来更像是对我的优化而不是基本特征,那么为什么我需要解决它(当条件不是时提供单独的语句作为定义即使原始语句的语法已经足够了,而不是相反的方式(编译器将原始语句视为定义,因为语法足够需要有一个定义)?

3 个答案:

答案 0 :(得分:13)

免责声明:我不是标准大师。

我建议你阅读以下两点:

http://www.stroustrup.com/bs_faq2.html#in-class
和:
What is the difference between a definition and a declaration?

  

为什么static const int NumTurns = 5不是定义?是不是NumTurns   这里初始化为值5,而不是a   声明和定义一起发生,它被称为   初始化?

在高级别中,定义(与声明相对)实例化或实现实体(变量,类,函数)。
如果是变量,则定义使变量在程序存储器中分配。
如果是函数 - 定义提供了可以编译为汇编指令的指令。*

代码行

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 ++哲学。 当你让编译器决定时,例如在哪里声明一个静态积分const, 作为开发人员,我可能会提出以下问题:

1)如果我的代码是仅限标题的库,会发生什么? where 我的编译器应该声明变量吗?在遇到的第一个cpp文件中? 在包含main的文件中?

2)如果编译器决定在哪里声明变量,我是否有任何保证 这个变量将与其他静态变量(我手动声明)一起声明,从而保持 缓存局部紧张?

一旦我们批准“让竞争对手疯狂”的心态,我们就会提出越来越多的问题。 我认为这是Java和C ++之间的根本区别。

Java:让JVM做它的测试 C ++:让开发人员进行他的分析。

最终,在C ++中,编译器检查所​​有内容是否生成并将代码转换为二进制, 不做工作而不是开发人员。

*或字节码,如果您使用托管C ++(boo ...)

答案 1 :(得分:7)

单定义规则的一个后果是类的静态成员只能有一个定义。但是,如果多个编译单元定义它,则程序中的每个编译单元都有一个定义。实际效果是,如果不违反单一定义规则,声明就不能成为定义。在实践中,链接器通常不够聪明,无法解决这种多重定义。

静态积分常数不需要定义的原因是它没有必要。如果在类定义中初始化该值,则编译器只需在使用它时替换初始化值。实际上,这意味着不需要该值实际占用程序中的内存位置(只要没有代码计算该常量的地址,在这种情况下就需要定义)。

声明,定义,初始化实际上是独立的(尽管是相关的)概念。声明告诉编译器存在某些东西。定义是一种声明导致某些东西存在的声明(因此具有其他声明可见性的代码可以引用它) - 例如,为它分配内存。初始化是给予价值的行为。这种区别实际上发生在语言的其他部分。例如;

#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 ++中,如果一个编译单元看到一个声明但没有定义,编译器只是假定该定义在另一个编译单元中。通常(具有大量构建链),链接器稍后会检测是否存在必要的定义,因此链接阶段会失败。相反,如果每个需要存在定义的编译单元实际上都创建了一个,那么编译器就会破坏“一个定义规则”和一个典型的哑链接器 - 除了其他功能之外,它还不够智能地将重复定义的东西折叠成单个定义 - 会抱怨多重定义的符号。

答案 2 :(得分:5)

  

为什么静态const NumTurns = 5不是定义? NumTurns在这里没有初始化为值5,并且当声明和定义一起出现时,它不是称为初始化吗?

这是错误的问题。声明是通知编译器类型/变量/函数/存在的任何内容,以及它是什么。定义是指示编译器实际分配存储以保持所述实体的位置。

由于成员在类'声明中被“定义”(即 - 此时没有创建类的实例),所以这是一个声明。

为了将这个定义称为定义,您所依赖的等号只是结构成员的默认值,而不是初始化。

  

为什么静态积分常数不需要定义?

你自己回答了那个问题。这是因为编译器可以避免为它们分配任何存储,只需将它们放入使用的代码中即可。

  

为什么第二个代码片段在没有定义值时被认为是一个定义,但是包含该值的类中的声明仍然不是一个(基本上回到我的第一个问题)?

正如我之前所说,那是因为第二个代码为变量分配了存储空间。

  

是不是初始化定义?为什么这里没有违反“唯一定义”规则?

因为初始化不是定义。

  

如果编译器在程序中检测到需要定义的条件(例如,程序中使用了&amp; NumTurns),为什么不自动重新解释静态const int NumTurns = 5;作为宣言&amp;定义而不是仅声明?

因为定义会分配存储空间。更具体地说,因为如果编译器这样做,那么在不同的编译单元中将存在多个存储分配。这很糟糕,因为在链接期间,链接器只需要一个。链接器看到具有相同范围的同一变量的几个定义,并且不知道它具有相同的值。它所知道的是它无法将它们全部合并到一个位置。

为了避免这个问题,它会让您手动进行定义。您可以在cpp文件中定义它(即 - 不在标头中),从而将分配解析为链接器可以承载的一个特定文件。