我正在阅读this Stack Overflow question,我在该问题的代码中添加了一个构造函数,如下所示,
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
。在那个问题中,它得出结论,私有的名称是不可访问的,但类型是。那么类型和名称之间的区别通常是什么?
并说我有两个特定的场景。
以下两个声明之间的区别是什么?为什么我可以从b.i=5
获得输出auto b = f.Baz();
?
Foo::Bar b = f.Baz();
auto b = f.Baz();
如果我在typedef Bar B;
的公开部分添加Foo
,则以下内容之间有什么区别?
Foo::Bar b = f.Baz();
Foo::B b = f.Baz();
如果方案1和2之间存在差异?
答案 0 :(得分:21)
类型和名称之间有什么区别
类型没有,一个或多个名称。 Typedef and alias只是为类型创建新名称的方法。
public
和private
个关键字与不存在基础类型或成员的名称相关。
为了显式声明特定类型的对象,您需要该类型的名称。 auto
不需要这个。例如,如果您使用未命名的类as a return type,则此类没有名称,但仍可以在其上使用auto。
类型最多只能有一个“true name”。即使通过typedef或别名使用它,编译器也会以此名称(或实际上是此名称的原始版本)使用它。所以:
class A {};
typedef A B;
std::cout << typeid(B).name();
打印“A级”。未命名的对象不能被赋予“真实姓名”。但是,使用typedef
和decltype
时。可以创建新名称。如果此名称随后用于创建对象。 typeid().name
将打印新分配的名称。如果对象未命名以“真实姓名”开头,则会打印出名称。
方案:
不同之处在于,您首先使用的是私人声明的名称。哪个是非法的。这与类型推导的不同方式如何工作有关。正如Scott Meyers解释here。由于公共函数调用提供此类型,因此返回类型是公共的。但是,Bar
本身并不公开。这是一个小差异,但这就是原因。
这只是在设计中做出的决定。这是有道理的,有时您只需要在返回时使用结构。
同样如此。没有区别,但Foo::Bar
根本无法访问。
修改强>
你能给出一个类型没有名字的例子吗?是上面评论中未命名的联盟的一个例子吗?
如上所述here我使用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
有点特别,是fundamental type。 'int'确实是int类型的名称。但它绝不是唯一的名称int32_t
,例如,在大多数编译器上是完全相同类型的另一个名称(在其他系统上int16_t
相当于int
)。
std::cout << typeid(int32_t).name();
打印“int”。
注意:
我没有使用别名作为对象其他名称的指示符,因为这可能会导致与alias关键字混淆。
我从经验中收集了大部分内容。所以我可能错过了一些东西。
由于缺少一个更好的词,我使用了“真名”这个词。如果有人知道官方或更好的话,我会很高兴听到它:)。
答案 1 :(得分:6)
[前面的一些标准]
让我们同意auto
推论与模板参数推导相同:
[dcl.spec.auto] / P7
如果占位符是自动 type-specifier,推导出的类型是使用模板参数推导的规则
确定的
模板在编译期间受two-phase lookup的约束。访问控制适用于第一阶段的名称查找
[basic.lookup] / P1
重载解析(13.3)在名称查找成功后发生。访问规则(第11条)仅在名称查找和功能重载解析(如果适用)成功后才被考虑。只有在名称查找之后,函数重载解析(如果适用)和访问检查成功才会在表达式处理中进一步使用名称声明引入的属性
auto
和decltype(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
的语义就像隐式模板推导(规范性措辞也直接引用它)。
Someone else had this doubt before
现在回答:
如果您认为来电者无法访问名称Case 1
,则很容易同意 Foo::Bar
。
Case 2
也将名称公开给调用者,因此如果使用typedef'd名称,您的代码将很乐意编译。
答案 2 :(得分:3)
你链接的问题解释了很多,但作为对那里写的内容的补充......
主要区别在于,在第二行auto b=...
中,您让编译器推导出表达式的类型。您无法指定类型,因为隐藏了类型的名称。虽然类型可用(至少来自编译器)
您公开显示该类型的名称,以便可以使用它。
这是一个非常好的答案https://stackoverflow.com/a/13532882/3037915
要尝试回答标题中的问题,您可以考虑键入形状,并将类型名称作为用于引用特定形状的名称。即使隐藏了“形状”的名称,形状仍然存在并且可以使用。