为什么不能在C ++中重新定义类中的类型名称?

时间:2017-07-29 03:30:50

标签: c++ class scope typename

根据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 该部分还有一个注释:

  

虽然重新定义类型名称是错误的,但编译器不需要诊断此错误。有些编译器会悄悄接受这样的代码,即使程序出错也是如此。

3 个答案:

答案 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 ausing Foo = char*按顺序编译 。第一个Foo使用Foo的外部定义,即int。然后,创建内部范围Foo,类型为char*。之后,编译器开始编译函数体。对于函数meowFoo 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我们得到一个内部范围balAccount::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}}

感谢其他两个答案的启示。在我的帖子中有一些预测,这是我个人的推论。如果有任何错误,请随时纠正。