在C ++ 0X中评估auto类型

时间:2011-07-15 12:14:52

标签: c++ c++11

我正在使用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。 任何人都可以给我一个提示,我可以在标准中找到这种行为的定义吗?

5 个答案:

答案 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,这意味着b1Base类型的对象,因此它会打印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才能保留信息 - 允许简单的普通旧数据对象的大小合适。

http://en.wikipedia.org/wiki/Run-time_type_information

答案 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()