Подтвердить что ты не робот

Оценка типа для авто в С++ 0X

Я играю с автоматической функцией в стандарте С++ 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;
}

Он будет печатать базовые и производные.
Но почему auto& оценивается как ссылка на Derived, а не ref на 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;
}

возвращает базу для обоих типов. Итак, почему тип зависит от виртуальных функций? Компилятор, который я использую, - VS2010. Может ли кто-нибудь дать мне подсказку, где я могу найти определение этого поведения в стандарте?

4b9b3361

Ответ 1

В первом случае с виртуальной функцией (-ами):

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. Посмотрите сами:


Теперь второй случай: когда у вас нет виртуальной функции в Base, тогда dp указывает на подобъект объекта, созданный с помощью new

Base* dp = new Derived; //dp gets subobject

Вот почему вы получаете Base даже с auto &, потому что dp больше не является полиморфным объектом.

Ответ 2

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;

Ответ 3

Информация для typeid поступает из vtable - поэтому он использует фактический тип объекта, а не то, что может быть авто. В случае b1 dp нарезается до Base, поэтому vtable теперь является базовым. В случае b2 не было нарезки, поэтому vtable является оригиналом.

Во втором вопросе RTTI доступен только для полиморфных классов (по крайней мере, один виртуальный метод). Это связано с тем, что информация сохраняется только в том случае, если у вас есть vtable, что позволяет простым объектам Plain-old-data иметь нужный размер.

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

Ответ 4

Это не ссылка на производную, это ссылка на Base. Когда у вас есть полиморфный объект, typeid() эффективно становится виртуальным вызовом и возвращает наиболее производный тип. Причина, по которой ваш второй набор тестов возвращает базу оба раза, заключается в том, что виртуальных функций нет, поэтому typeid() возвращает статический тип объекта, который Base.

Другими словами, он не становится ссылкой на полученный, вы просто печатаете имя неправильно. Вам нужно будет сделать typeid(decltype(b2)).name(), чтобы убедиться, что вы смотрите на имя статического типа объекта.

Ответ 5

auto работает точно (но см. ниже), как будто вы берете весь тип переменной auto'ed и используете его как тип типа параметра шаблона функции. Все вхождения auto в типе заменяются параметром шаблона. Поэтому, если вы использовали auto& как автоматический тип переменной, тип параметра шаблона функции становится

template<typename T>
void f(T&); // auto& -> T&

Теперь пусть инициализатор переменной является аргументом в вызове f(initializer). Тип параметра функции, который вы получите, будет типом автоматической переменной. Например, если вы используете auto& как тип, подобный выше, то f станет шаблоном функции с типом параметра T&. Если инициализатор является выражением Base, тогда переменная заканчивается с типом Base&, потому что это будет выводимый тип параметра для вызова f(a_Base_argument).

Итак, используя auto, поскольку тип переменной auto'ed будет использовать шаблон функции с типом параметра T за кулисами. Вызов f(a_Base_argument) приведет к типу параметра Base. Это больше не ссылка на другой объект, а новая переменная Base.

Вышеупомянутый способ получить тип auto'ed верен для подавляющего большинства случаев, но С++ 0x имеет специальный регистр для инициализаторов формы { a, b, c } (т.е. с переменными инициализаторами, которые являются скопированными списками). В вашем случае у вас нет такого инициализатора, поэтому я проигнорировал этот особый случай.

Причина, по которой в одном из ваших случаев Derived вместо Base объясняется другими ответами. Чтобы получить то, что вы искали с помощью typeid, вы можете сначала получить объявленный тип переменной (используя decltype), а затем передать это как тип typeid, как в

typeid(decltype(b2)).name()