根据C ++ Primer一节,7.4.1类型名称是特殊的:
通常,内部作用域可以从外部作用域重新定义名称,即使该名称已在内部作用域中使用过。但是,在类中,如果成员使用外部作用域中的名称并且该名称是类型,则该类可能不会随后重新定义该名称。
因此,例如:
typedef double Money;
class Account {
public:
Money balance() { return bal; }
private:
typedef double Money;
Money bal;
};
int main() {
typedef double Money;
Money asset;
typedef double Money;
return 0;
}
编译上面的示例时,它会抱怨:
a.cc:6:24: error: declaration of ‘typedef double Account::Money’ [-fpermissive]
typedef double Money;
^
a.cc:1:16: error: changes meaning of ‘Money’ from ‘typedef double Money’ [-fpermissive]
typedef double Money;
那么为什么我们不能在类中重新定义类型名称,但是我们可以在内部范围内吗?
我的编译器版本是g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
该部分还有一个注释:
虽然重新定义类型名称是错误的,但编译器不需要诊断此错误。有些编译器会悄悄接受这样的代码,即使程序出错也是如此。
答案 0 :(得分:9)
这不是类型所特有的。 [basic.class.scope] / 2:
类
N
中使用的名称S
应引用其中的相同声明 上下文以及在S
的完成范围内重新评估时。没有 违反此规则需要诊断。
原因是类范围中的名称查找有点特殊。考虑:
using Foo = int;
struct X {
Foo a; // ::Foo, i.e., int
void meow() {
Foo b = a; // X::Foo; error: no conversion from int to char*
}
using Foo = char*;
};
成员函数体中的名称查找会考虑所有类成员,无论是在成员函数之前还是之后声明(否则,在类定义中定义的成员函数将无法使用稍后在类中声明的数据成员)。结果是你得到两个具有不同含义的Foo
,即使它们都在词法成员Foo
的声明之前有词法。这很容易导致极其混乱和脆弱的代码,因此标准禁止它。
答案 1 :(得分:3)
当编译器读取行
时 Money balance() { return bal; }
在类定义中,它已经在类之外使用Money
的定义。这就行了
typedef double Money;
在课堂内一个问题。但是,在类中使用之前,您可以在类中使用重新定义Money
。以下是好的。
typedef double Money;
class Account {
public:
typedef double Money;
Money balance() { return bal; }
private:
Money bal;
};
引用的关键点是:
因此,该课程可能随后重新定义该名称。
答案 2 :(得分:1)
我想尝试回答你的评论中的一些问题。
评论1
“但是在函数main中,我们可以重新定义typedef double Money,即使它是在语句Money资产之后定义的”
所以你问,为什么typedef
标识符可以在非成员函数中定义多次(在非类作用域中)?
答案在这里:Repeated typedefs - invalid in C but valid in C++?
评论2
因此,在这个例子中,两个具有不同含义的Foo在词汇Foo b = a之间在词法上位于函数meow中。然后编译器无法确定b的类型。它是否正确?
编译器可以确定该代码块中的b的类型。 B的类型是char*
,而a的类型是int
。
虽然两个具有不同含义的Foo
词汇在函数Foo b = a
中的语句meow
之前,Foo
定义为int
,但在Foo
之前定义为char*
。该书说名称查找过程是不同的:
•首先,编制成员声明 •只有在看到整个类之后才编译函数体。
所以在第一步中,在编译成员声明时,
Foo a
和using Foo = char*
按顺序编译 。第一个Foo
使用Foo
的外部定义,即int
。然后,创建内部范围Foo
,类型为char*
。之后,编译器开始编译函数体。对于函数meow
,Foo b
使用内部作用域Foo
,即char*
,而对于a
,已经在第一步中编译的是Foo
, int typedef
。这就是转换错误的发生方式。
评论3
我想知道为什么“该课程可能不会随后重新定义该名称”。但是“内部作用域可以从外部作用域重新定义名称,即使该名称已经在内部作用域中使用过。” -
R Sahu 的观点(我认为这是本书想要说的)是如果你真的想要重新定义typedef double Money;
class Account {
public:
Money balance() { return bal; }
private:
typedef double Money;
Money bal;
};
标识符,你只能在课程开始时这样做。因此,在上下文中不会有任何关于该标识符的“含糊不清”。
<强>摘要强>:
允许:
(这不能用g ++编译(因为标准禁止这个),但可以在Visual Studio中,因为逻辑上这里没有冲突。)
using Foo = int;
struct X {
Foo a; // ::Foo, i.e., int
void meow() {
Foo b = a; // X::Foo; error: no conversion from int to char*
}
using Foo = char*;
};
很容易造成这样的事情:
(无法在g ++和Visual Studio中编译,因为逻辑上存在冲突。)
typedef
所以,如果你真的想重新定义一个类中的typedef double Money;
class Account {
public:
typedef double Money;
//put the redefine statement in the very beginning
Money balance() { return bal; }
private:
Money bal;
};
标识符。只做这个:
(可以在g ++和Visual Studio中编译,因为逻辑上这里没有冲突,标准只允许这样做。)
typedef double Money;
class Account {
public:
Money balance() { return bal; }
private:
typedef double Money;
Money bal;
};
<强> PS:强>
解释代码:
balance
这些代码逻辑正确,但代表标准它被禁止。与上面提到的编译步骤相同,首先编译函数Money
的声明,因此这里的Money
是外部typedef double Money
。然后complie Money
我们得到一个内部范围bal
,Account::Money
的类型是typedef double Money;
class Account {
public:
Money shit = 12.34; //outside money, type is double
private:
typedef string Money;
Money bal; //bal is string not double
};
而不是外部范围。
所以实际上你可以用 Visual Studio 编译器完成这项操作,但不能用 g ++ 执行此操作:
{{1}}
感谢其他两个答案的启示。在我的帖子中有一些预测,这是我个人的推论。如果有任何错误,请随时纠正。