为什么不能在C++中重新定义类中的类型名称?
Why can't redefine type names in class in C++?
根据本书C++入门部分,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
.
该部分中还有一个注释:
尽管重新定义类型名称是一个错误,但编译器不需要诊断此错误。一些编译器会悄悄地接受这样的代码,即使程序出错了。
这不是类型所独有的。[基本.class.范围]/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
声明之前。这很容易导致代码非常混乱和脆弱,因此标准禁止它。
当编译器读取该行时
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;
};
引文中的关键点是:
因此,该类随后可能不会重新定义该名称。
我想尝试回答您评论中的一些问题。
评论 1
"但是在函数 main 中,我们可以重新定义 typedef double Money,即使它是在语句 Money asset 之后定义的">
所以你在问,为什么typedef
标识符可以在非成员函数(在非类范围内)中多次定义?
答案在这里:重复的typedefs - 在C中无效,但在C++中有效?
评论2
因此,在这个例子中,两个具有不同含义的 Foos 在词法上都位于语句 Foo b = a in 函数 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
。这就是转换错误发生的方式。
评论3
我想知道的是为什么"类随后可能不会重新定义该名称"。但是"内部作用域可以从外部作用域重新定义名称,即使该名称已在内部作用域中使用。
R Sahu的观点(我认为这也是这本书想说的)是,如果你真的想重新定义一个typedef
标识符,你只能在课程的一开始就这样做。因此,上下文中不会有任何关于该标识符的"歧义"。
总结:
允许此操作:
(这不能在g++中编译(因为标准禁止这样做),但可以在Visual Studio中编译,因为从逻辑上讲,这里没有冲突。
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*;
};
因此,如果您真的想在类中重新定义typedef
标识符。仅执行以下操作:
(可以在 g++ 和 Visual Studio 中编译,因为从逻辑上讲,这里没有冲突,标准只允许这样做。
typedef double Money;
class Account {
public:
typedef double Money;
//put the redefine statement in the very beginning
Money balance() { return bal; }
private:
Money bal;
};
附注:
解释代码:
typedef double Money;
class Account {
public:
Money balance() { return bal; }
private:
typedef double Money;
Money bal;
};
这些代码在逻辑上是正确的,但对于标准代码,它是被禁止的。与上面提到的编译步骤相同,首先编译函数balance
的声明,所以这里Money
是外部Money
。然后遵守typedef double Money
我们得到一个内部范围Money
并且bal
的类型Account::Money
而不是外部范围。
所以实际上你可以用Visual Studio编译器来做到这一点,但不能用g++:
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
};
感谢其他两个答案的启发。我的帖子中有一些预测,这是我个人的推论。如果有任何错误,请随时更正。
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- 列表参数的类型定义
- 使用模板化的键类型定义 std::map,该键类型基于作为参数接收的函数
- 关于 C++ 中的函数类型定义
- C++(和 ROS) - 包含与前向声明引用,设置默认值和类型定义
- 将使用/类型定义限制为类范围
- 模板类型定义?
- C++:模板类的类型定义
- 如何对命名空间限定类型进行类型定义?
- 此递归模板类型定义是否有效C++?
- 具有调整对齐方式的类型定义
- C++从抽象类型定义类成员
- 用于C++代码的 API 监视器类型定义 (XML)
- 如何将result_of与函数类型定义一起使用
- 在C++的适当类型定义位置
- 如何根据模板类型定义浮点常量?
- 如何为缺少预定义运算符而不扩展命名空间"std"的标准类型定义运算符>> (istream &, ...)?
- 参数化类的别名(或类型定义)内部类
- 如果我想从类型"T"定义元素的容器(来自 STL),那么"T"必须使用默认构造函数?