Оценка типа для авто в С++ 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.
Может ли кто-нибудь дать мне подсказку, где я могу найти определение этого поведения в стандарте?
Ответы
Ответ 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()