我正在使用C ++ 0X标准中的自动功能,但我很困惑如何做出类型的决定。请考虑以下代码。
struct Base
{
virtual void f()
{
std::cout << "Base::f" << std::endl;
}
};
struct Derived : public Base
{
virtual void f()
{
std::cout << "Derived::f" << std::endl;
}
};
int main()
{
Base* dp = new Derived;
auto b1 = *dp;
auto& b2 = *dp;
std::cout << typeid(b1).name() << std::endl;
std::cout << typeid(b2).name() << std::endl;
}
它将打印Base和Derived
但是为什么auto&
被评估为参考衍生而不是参考基础?
更糟糕的是将代码更改为:
struct Base{};
struct Derived : public Base{};
int main()
{
Base* dp = new Derived;
auto b1 = *dp;
auto& b2 = *dp;
std::cout << typeid(b1).name() << std::endl;
std::cout << typeid(b2).name() << std::endl;
}
返回两种类型的Base。那么为什么类型取决于虚函数? 我使用的编译器是VS2010。 任何人都可以给我一个提示,我可以在标准中找到这种行为的定义吗?
答案 0 :(得分:11)
auto
都会产生Base
,而不是派生。在第一种情况下,您正在切割对象(在父级别进行复制),而在第二种情况下,因为它是一个引用,您将获得Base&
到实际的Derived
对象。这意味着所有虚函数调用都将被调度到Derived
级别的最终覆盖。
typeid
运算符对多态类型的行为与非多态类型不同。如果应用于对多态类型的引用,它将在运行时执行类型检查并生成实际对象的类型。如果它应用于对象或对非多态类型的引用,则它将在编译时解析为对象或引用的静态类型。
要验证auto
推断的内容,您可以使用略有不同的测试:
void test( Base& ) { std::cout << "Base" << std::endl; }
void test( Derived& ) { std::cout << "Derived" << std::endl; }
然后调用该函数并查看它正在解析的类型。我希望编译器选择第一个重载,因为auto& a = *dp;
应该等同于Base& a = *dp;
答案 1 :(得分:7)
在虚拟功能的第一种情况下:
auto b1 = *dp;
auto& b2 = *dp;
第一行导致object slicing,这意味着b1
是Base
类型的对象,因此它会打印Base
。第二行是创建Base&
类型的对象,它是对dp
指向的实际对象的引用。实际对象的类型为Derived
。因此它打印Derived
。
第二行相当于以下内容:
Base & b = *dp; //this is also a reference to the actual object
std::cout << typeid(b).name() << std::endl;
它会打印什么? Base
?不会。这将打印Derived
。自己看看:
现在是第二种情况:当Base
中没有虚函数时,dp
指向使用new
创建的对象的子对象
Base* dp = new Derived; //dp gets subobject
这就是为什么即使Base
获得auto &
,因为dp
不再是多态对象。
答案 2 :(得分:2)
typeid的信息来自vtable - 因此它使用对象的实际类型,而不是auto可能的类型。在b1的情况下,dp被切成了一个Base,所以vtable现在是一个Base。在b2的情况下,没有切片,所以它的vtable是原始的。
对于第二个问题,RTTI仅适用于多态类(至少一个虚方法)。这是因为只有拥有vtable才能保留信息 - 允许简单的普通旧数据对象的大小合适。
答案 3 :(得分:1)
它不是对派生的引用,而是对Base的引用。当你有一个多态对象时,typeid()
实际上成为一个虚拟调用并返回最派生的类型。第二个测试集两次返回base的原因是因为没有虚函数,所以typeid()
返回对象的静态类型,即Base
。
换句话说,它不会成为派生的参考,你只是打印出错误的名称。您必须执行typeid(decltype(b2)).name()
以确保您正在查看对象的静态类型的名称。
答案 4 :(得分:1)
auto
完全正常工作(但请参见下文),就好像您采用整个auto'ed变量类型并将其用作函数模板参数类型的类型一样。类型中的所有auto
次出现都由模板参数替换。因此,如果您使用auto&
作为自动变量类型,则函数模板的参数类型将变为
template<typename T>
void f(T&); // auto& -> T&
现在让变量初始值设定项成为调用f(initializer)
中的参数。您获得的函数参数的类型将是auto'ed变量的类型。例如,如果您使用auto&
作为上述类型,则f
将成为参数类型为T&
的函数模板。如果初始化程序是Base
表达式,则变量最终具有类型Base&
,因为这将是调用f(a_Base_argument)
的推导参数类型。
因此,使用auto
作为自动变量类型将在幕后使用参数类型为T
的函数模板。调用f(a_Base_argument)
将以参数类型Base
结束。这不再是对另一个对象的引用,而是一个新的Base
变量。
上面获取auto'ed类型的方法对于绝大多数情况都是正确的,但是C ++ 0x对于{ a, b, c }
形式的初始化器有一个特殊情况(即使用支撑列表的变量初始化器) )。在你的情况下,你没有这样的初始化器,所以我忽略了这种特殊情况。
其中一个案例Derived
而不是Base
的原因可以通过其他答案解释。要使用typeid
获取您要查找的内容,您可以先获取变量的声明类型(使用decltype
),然后将其作为类型传递给typeid
,如
typeid(decltype(b2)).name()