我正在玩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&
被评估为ref to Derived而不是ref to Base?
更糟糕的是,把代码改成这样:
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。谁能给我一个提示,我在哪里可以找到这个行为的定义在标准?
auto
在这两种情况下都是生成Base
,而不是派生的。在第一种情况下,您正在对对象进行切片(在父级复制),而在第二种情况下,由于它是一个引用,因此您将获得实际Derived
对象的Base&
。这意味着所有虚函数调用都将被分派到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;
在第一种情况下使用虚函数:
auto b1 = *dp;
auto& b2 = *dp;
第一行导致对象切片,这意味着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
。你自己看吧:
- http://www.ideone.com/9IjPc
现在第二种情况:当你在Base
中没有虚函数时,dp
指向用new
创建的对象的子对象
Base* dp = new Derived; //dp gets subobject
这就是为什么你得到Base
甚至与auto &
,因为dp
不再是一个多态对象。
typeid的信息来自虚函数表——因此它使用对象的实际类型,而不是auto可能是什么。在b1的情况下,dp被切成基底,所以虚表现在是基底1。在b2的例子中,没有切片,所以它的vtable是原始的。
对于第二个问题,RTTI只适用于多态类(至少一个虚方法)。这是因为只有当您有一个虚函数表时才保留信息——允许简单的Plain-old-data对象具有合适的大小。
http://en.wikipedia.org/wiki/Run-time_type_information
它不是对derived的引用,而是对Base的引用。当您有一个多态对象时,那么typeid()
有效地成为一个虚拟调用并返回最派生的类型。第二个测试集两次都返回base的原因是因为没有虚函数,所以typeid()
返回对象的静态类型,即Base
。
换句话说,它并没有成为派生的引用,你只是把名字打印错了。您必须执行typeid(decltype(b2)).name()
,以确保您正在查看对象的静态类型的名称。
auto
的工作原理与您使用整个自动变量类型并将其用作函数模板参数类型的类型完全相同(但见下文)。该类型中出现的所有auto
都被模板参数替换。因此,如果使用auto&
作为自动变量类型,则函数模板的参数类型变为
template<typename T>
void f(T&); // auto& -> T&
现在让变量初始化器作为调用f(initializer)
的实参。您获得的函数参数的类型将是自动变量的类型。例如,如果您使用auto&
作为上面的类型,那么f
将成为参数类型为T&
的函数模板。如果初始化式是Base
表达式,那么变量最终具有类型Base&
,因为这将是调用f(a_Base_argument)
的推导参数类型。
因此,使用auto
作为自动变量类型将在后台使用参数类型为T
的函数模板。调用f(a_Base_argument)
将以参数类型Base
结束。这不再是对另一个对象的引用,而是一个新的Base
变量。
上述获得自动类型的方法在绝大多数情况下都是正确的,但是c++ 0x对于{ a, b, c }
形式的初始化项有一个特殊的情况(即使用括号列表的变量初始化项)。在你的例子中,你没有这样的初始化器,所以我忽略了这个特殊情况。
为什么在你的一个案例中Derived
而不是Base
的原因是由其他答案解释的。要使用typeid
获得您正在寻找的内容,您可以首先获得变量的声明类型(使用decltype
),然后将其作为类型传递给typeid
,如
typeid(decltype(b2)).name()