On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 28.07.12 14:51. Заголовок: Поиск неквалифицированных имен


На одном из форумов был задан интересный вопрос, который так и не получил внятного ответа. Автор вопроса приводит такой код:

namespace X 
{
namespace Y
{
class Outer {};

namespace Z
{
class Inner
{
void callMe(Outer* param);
};
}
}
}

using namespace X::Y::Z;

void Inner::callMe(Outer* param)
{
}


По утверждению автора вопроса этот код компилируется g++, в то время как MS VС++ выдает сообщение об ошибке, что Outer - не объявленный идентификатор.

В связи с этим возникает вопрос, какой компилятор корректно компилирует этот код,?

Поразмыслите пару дней, а затем можете почитать написанный ниже на мой взгляд исчерпывающий ответ.



Спасибо: 0 
ПрофильЦитата Ответить
Ответов - 2 [только новые]





ссылка на сообщение  Отправлено: 28.07.12 14:54. Заголовок: Так как уже достаточ..


Так как уже достаточно много людей посмотрело данный вопрос, то пора уже дать ответ на него.

Ответ выглядит тривиальным. И я так думал до вчерашнего дня. Но, поэкспериментировав с поиском имен на MS VC++ 2010, я стал сомневаться, а действительно ли я все правильно понимаю? Могу лишь с уверенностью сказать, что я обнаружил очередной баг MS VC++ 2010, но тем не менее это не развеяло мои сомнения. Но об этом я напишу в следующей теме через пару дней. А пока ответ на данный вопрос.

Сначала рассмотрим, какие имена присутствуют в глобальном пространстве имен, если исключить директиву using. Имеется лишь одно имя - это имя пространства имен X (если не считать функции main).
Когда была включена директива using namespace X::Y::Z; , то в глобальное пространство имен добавились члены указанного пространства имен X::Y::Z, а именно имя класса Inner.

Поэтому вполне правомерно написание void Inner::callMe в определении функции, так как неквалифицированное имя Inner будет найдено в глобальном пространстве имен.

Теперь, что касается имени Outer в определении функции. Компилятор будет сначала искать это имя в области определения класса Inner. Но там имя Outer не определяется. Тогда компилятор будет искать это имя в пространстве имен, в котором заключено определение класса. В данном случае это глобальное пространство имен, так как имя Inner было туда помещено с помощью директивы using.
Но в глобальном пространстве имен нет определения этого имени. Поэтому компилятор должен выдать сообщение об ошибке, что имя Outer не найдено.

Поэтому можно сделать вывод, что компилятор MS VC++ 2010 в данной ситуации ведет себя в соответствии со стандартом, тогда как g++ ведет себя не корректно. Более того такое поведение компилятора g++ ставит под сомнение вообще его способность правильно искать имена. Я покажу ниже, что я имею в виду. А пока возникает вопрос, как сделать так, чтобы компилятор увидел имя Outer?

Наиболее простой способ, это нужно просто "подцепить" имя Outer к именам пространства имен Z, когда имена этого пространства мы переносили в глобальное пространство. Для этого достаточно в свою очередь включить директиву using в пространство имен Z. Выглядеть это будет так

namespace X  
{
namespace Y
{
class Outer {};

namespace Z
{
using namespace Y;
class Inner
{
void callMe(Outer* param);
};
}
}
}

using namespace X::Y::Z;

void Inner::callMe(Outer* param)
{
}


То есть в пространство имен Z вчтавлена директива using namespace Y;. Что изменилось? Теперь в поиск имен мы для компилятора добавили дополнительный шаг - просматривать пространство имен Y.

Такой код должен нормально компилироваться без сообщений об ошибки.

Но еще лучше не засорять глобальное пространство имен, то есть не включать в него директиву using. Это к тому же упростило поиск неквалифицированных имен. Соответствующий код будет выглядеть так

namespace X  
{
namespace Y
{
class Outer {};

namespace Z
{
class Inner
{
void callMe(Outer* param);
};
}
}
}

void X::Y::Z::Inner::callMe(Outer* param)
{
}


Тогда алгоритм поиска будет следующим. Сначала имя Outer будет искаться в определении класса Inner. Так как там оно не будет найдено, то это имя будет искаться в пространстве имен, где находится определение класса Inner, то есть в пространстве имен Z. Так как и там имя Outer не будет найдено, то поиск продолжится в пространстве имен, которое в себе заключает пространство имен Z, то есть в пространстве имен Y. И там компилятор, наконец-то, найдет имя Outer, поэтому код должен нормально скомпилироваться.

Теперь вернемся к моему замечанию, что поведение компилятора g++ вызывает много вопросов. Для этого изменим первоначальный пример и перегрузим имя функции callMe в классе Inner.

namespace X  
{
namespace Y
{
class Outer {};

namespace Z
{
class Inner
{
void callMe(Outer* param);
void callMe(int* param);
};
}
}
}

using namespace X::Y::Z;

typedef int Outer;

void Inner::callMe(Outer* param)
{
}


Возникает вопрос, какая из двух функций callMe определяется? Если следовать логике поиска имен компилятора g++, то должна возникнуть ситуация неоднозначности, так как компилятор g++ должен найти два определения имени Outer. Первое - это то, которое он находил ранее в вышеприведенном примере, а второе - это добавленное нами имя с помощью typedef.
Компилятор MS VC++ 2010 будет вести себя адекватно. То есть для него никакой неоднозначности не будет, и данное определение callMe будет соответствовать объявлению void callMe( int *param ); Вы в этом можете самостоятельно убедиться, задав тело этой функции, например, следующим образом

void Inner::callMe(Outer* param)  { std::cout << "void Inner::callMe( int * )\n"; }


А в функцию main нужно будет вставить следующий код

int main() 
{
Inner obj;
int i = 0;
obj.callMe( &i );
}


Интересно так же рассмотреть случай, когда мы "подцепляем" пространство имен Y к пространству имен Z с помощью директивы using, как это было показано выше, и в то же время в глобальном пространстве имен имеется объявление typedef int Outer; Как тогда компилятору сообщить, какая из двух функций определяется? В этом случае мы должны квалифицировать имя параметра. Например, для функции callMe с параметром int * определение будет выглядеть следующим образом

namespace X  
{
namespace Y
{
class Outer {};

namespace Z
{
using namespace Y;
class Inner
{
void callMe(Outer* param);
void callMe(int* param);
};
}
}
}

using namespace X::Y::Z;

typedef int Outer;

void Inner::callMe(::Outer* param)
{
}


То есть имя Outer было квалифицировано как имя из глобального пространства имен: ::Outer, которое является алиасом типа int.
На этом мой анализ исходного вопроса заканчивается. Если у кого-то есть замечания и возражения, то будет интересно их услышать.

Я в начале написал, что у меня возникли сомнения в моих знаниях того, как происходит поиск имен, так как экспериментируя с подобным поиском, я столкнулся с непонятными для меня сообщениями компилятора MS VC++ 2010. Но об этом я напишу через пару дней в новой теме.


Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 28.07.12 14:55. Заголовок: Может показаться пос..


Может показаться после прочитанного, что изначальной вопрос прост и его решение тривиально. Но на самом деле это не совсем так. Это так кажется только тогда, когда уж все "разжевали". А когда сам программист сталкивается с такой проблемой, то часто теряются. Дело в том, что многие программисты полагаются на компилятор, и их свои знания С++ проверяют тем, компилируется код или нет. Если код компилируется, то такой программист считает, что он правильный. Если код не компилируется, то программист считает, что это именно он допустил ошибку. То есть в виду отсутствия знаний стандарта у программиста вырабатывается слепая вера компилятору. И когда он сталкивается с тем, что код одним известным компилятором компилируется, а другим не менее известным компилятором не компилируется, то такой программист ввергается в полную растерянность.

Чтобы это прочувствовать, я решил включить ссылку на сайт, откуда я взял этот вопрос, на который подробно ответил. Думаю, что будет полезно почитать обсуждение этого вопроса на том сайте, чтобы прочувствовать весь контекст описанной ситуации.
]Почитайте http://www.sql.ru/forum/actualthread.aspx?tid=877884[/url]

Спасибо: 0 
ПрофильЦитата Ответить
Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 19
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет