C++中"type"和"name"有什么区别?

23

我正在阅读这个Stack Overflow问题,并在该问题的代码中添加了一个构造函数,如下所示:

class Foo {
    struct Bar { 
        int i; 
        Bar(int a = 5) :i(a) {}; 
    };

  public:
    Bar Baz() { return Bar(); }
};

int main() {
    Foo f;
    // Foo::Bar b = f.Baz();  // error
    auto b = f.Baz();         // ok
    std::cout <<"b.i="<< b.i<<endl;
    return 0;
}

代码输出 b.i=5。在那个问题中,它得出结论私有名称不可访问,但类型是可访问的。所一般来说,类型和名称之间有什么区别?
再举两个具体情况。
  1. What's the difference between the following two declarations? Why can I get the output b.i=5 from auto b = f.Baz();?

    Foo::Bar b = f.Baz();
    auto b = f.Baz();
    
  2. If I add typedef Bar B; in the public part of Foo, what is the difference between the following?

     Foo::Bar b = f.Baz();
     Foo::B   b = f.Baz(); 
    
场景1和场景2之间有什么区别吗?

1
我认为私有“类型”并不存在。只有私有“成员”,这仅意味着它们的“名称”不可访问。类型本身不受访问控制。 - dyp
dyp 是正确的,名称和类型是两个不同的概念,在编译阶段它们被不同地处理。 - Marco A.
3个回答

21

类型和名称有什么区别?

一个类型可以没有、一个或多个名称。typedef 和alias 只是为类型创建一个新名称的简单方式。

publicprivate 关键字与名称相关,而不是底层类型或成员。

要显式声明特定类型的对象,您需要该类型的名称。 auto 不需要这样做。例如,如果您将一个无名类用作返回类型,则该类没有名称,但可以在其上使用 auto。

一个类型始终最多只有一个 'true name'。即使通过 typedef 或 alias 使用它,编译器也会使用此名称(实际上是该名称的原始版本)。所以:

class A {};
typedef A B;
std::cout << typeid(B).name();

打印“class A”。未命名的对象无法获得“真实名称”。但是,当使用typedefdecltype时,可以创建一个新名称。如果使用此名称创建对象,则typeid().name将打印新分配的名称。如果对象一开始就没有被命名,则将打印“真实名称”。


场景:

  1. 区别在于第一个中你使用了一个私有声明的名称,这是非法的。这与类型推断的不同方式有关。正如Scott Meyers在这里所解释的那样。由于公共函数调用提供了此类型,返回类型是公共的。然而,Bar本身并不是公共的。这只是一个小差别,但这就是原因。

    这只是在设计中做出的一个决定。有时候你只想在返回结构体时使用它。

  2. 这里也是一样的。没有区别,但是Foo::Bar是无法访问的。


编辑

你能举个类型没有名称的例子吗?上面评论中的未命名联合体是一个例子吗?

此处所述,我使用lambda函数如下:

auto f = [] () -> struct {int x, y ; } { return { 99, 101 } ; } ;

如果不使用auto或decltype,就无法创建变量f,因为它的类型没有名称。这是另一种没有名称的类型的示例。

struct foo
{
    struct{
        int x;
        int y;
    } memberVar;
};

将允许您执行以下操作:

foo bar;

auto baz = bar.memberVar;

std::cout << baz.x;

这将导致一堆初始化的东西,但你有了想法:)。这里memberVar的类型是匿名的,这使得无法明确定义baz

int被认为是int类型的名称吗?

int有点特殊,它是基本类型。'int'确实是int类型的名称,但这绝不是唯一的名称,例如int32_t就是大多数编译器上完全相同类型的另一个名称(在其他系统上,int16_t等同于int)。
std::cout << typeid(int32_t).name(); 

打印“int”。

注:

  • 我避免使用别名作为对象其他名称的指示器,因为这可能会与别名关键字混淆。

  • 大部分是从经验中积累的。所以我可能会错过一些细节。

  • 由于缺乏更好的词语,我使用了“真实名称”的表达方式。如果有人知道官方或更好的词语,我很乐意听到它们 :)。


@laurisvr,您能给出一个类型没有名称的示例吗?上面评论中的未命名联合体是否是这种情况的示例? int 是否被视为 int 类型的名称? - Allanqunzi
1
@Allanqunzi 我编辑了我的帖子,包括一些例子。并进一步阐述了命名的工作原理。 - laurisvr
请注意,C++ 禁止在返回或参数类型中定义新类型,如this answer所述。然而,还有其他一些方法可以创建返回未命名类型的函数。(我将在几分钟内添加其中一些。) - dyp
@dyp "有些架构的编译器中,int类型是16位的。" 你说得对。我犯了一个愚蠢的疏忽。我已经相应地更新了答案。 - laurisvr
以下是一些合法的方法,可以返回未命名类型:http://coliru.stacked-crooked.com/a/9d1124590c91d5fa - dyp

6

[以下是一些标准语]

我们可以认为auto推导的工作方式与模板参数推导相同:

[dcl.spec.auto]/p7

如果占位符是自动类型说明符,则使用模板参数推导规则确定推断类型。

在编译期间,模板会受到两阶段查找的限制。访问控制会在第一阶段中应用于名称查找。

[basic.lookup]/p1

重载决议(13.3)发生在名称查找成功之后。访问规则(第11条)仅在名称查找和函数重载解析(如果适用)成功后考虑。只有在名称查找、函数重载解析(如果适用)和访问检查都成功之后,名称声明引入的属性才会在表达式处理中进一步使用。autodecltype(auto)通常是一个推导类型的占位符,而且确实如此, [temp.arg] / p3 说:

模板参数的名称应在其用作模板参数的点处可访问

这里没有涉及名称,只涉及类型。访问控制适用于名称,类型可以映射到0、1或多个名称,并且在上述代码中使用auto时,您正在处理的就是这种语义:它在语义上等同于模板推断的语义,这是有意设计的。

[class.access]/p4

访问控制适用于所有名称,无论这些名称是从声明还是表达式中引用。[...] 不考虑typedef所引用实体的可访问性。例如

class A {
  class B { };
public:
  typedef B BB;
};
void f() {
  A::BB x; // OK, typedef name A::BB is public
  A::B y; // access error, A::B is private
}

为了让自己相信以下内容,请看涉及到模板参数推导的同一段代码(在概念上等同于使用auto版本)。
template<class T> 
T deduce(T t) {
    return t;
} 

class Foo {
    struct Bar{ 
        int i; 
        Bar(int a = 5):i(a){}; 
    };
public:

  Bar *getB() { return new Bar(5); } // Leaks, doesn't matter for clarity's sake
};

int main() {
    Foo f;
    std::cout <<"b.i="<< deduce(f.getB())->i <<std::endl; // Prints 'b.i=5'
    return 0;
}

实时示例

在上面的代码中,名称不涉及,因此访问控制不适用。类型确实涉及。

auto 的语义类似于隐式模板推导(规范措辞也直接引用了它)。

之前有其他人有过这个疑问


现在来看答案:
如果您考虑到调用者无法访问名称“Foo :: Bar”,那么Case 1很容易同意。 Case 2向调用者公开了名称,因此如果使用typedef的名称,则您的代码将可以编译。

3

你提供的问题解释了很多,但作为补充,以下是主要区别:

  1. 第二行 auto b=... 中的主要区别在于你让编译器推断表达式的类型。你不能指定类型,因为类型名称是隐藏的。但是类型仍然可用(至少从编译器角度来看)。

  2. 你公开了类型名称,以便可以使用它。

这是一个非常好的答案https://dev59.com/YWYr5IYBdhLWcg3wnrd0#13532882

为了尝试回答标题中的问题,你可以将类型视为形状,将类型名称视为你用来引用特定形状的名称。即使“形状”的名称被隐藏,该形状仍然存在并且可以使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接