Как стать автором
Обновить

Комментарии 70

Во воркэраундах без скобок у вас будет
а) лишнее копирование объекта, если я не ошибаюсь
б) если конструктор объявлен как explicit, то B b = A(a) не прокатит вообще. Кажется (давно с++ не трогал)
а) Лишнего копирования не будет
б) Действительно, с explicit не прокатит. Придётся писать B b = B(A(a)) — кстати, даже в этом случае лишнего копирования не будет. Вот только, если в качестве B выступает длинное шаблонное выражение, писать его два раза как-то очень не хочется
UPD: ошибаюсь, в варианте с дополнительной переменной действительно будет лишнее копирование a.
>> длинное шаблонное…
typedefы рулят :) правда, писать их тоже лень :)
У Скотта Мейерса в книге «Эффективное использование STL» тоже есть такой неочевидный пример в «Совете №6». Там приведён забавный пример:

ifstream dataFile(«file.txt»);
list<int> data(istream_iterator<int>(dataFile), istream_iterator());

Да, там у него из-за жутких наименований типов вообще сложно понять было :)
Интересно, кто-нибудь хоть раз в жизни использовал istream_iterator?
Достаточно часто вижу, удобная штука. А почему, собственно, нет?
название длинное
С появлением C++0x будет ещё один workaround:
B b{A(i)};
эту штуку компилятор уж никак не сможет спутать с функцией.
А вообще, я жду не дождусь, когда Бьярн Страуструп издаст мемуары, в которых признается, что разработку с++ ему проплатил ZOG для того, чтобы замучить и без того забитых программистов. Тогда я напишу ему открытое письмо, в котором буду тыкать в него пальцем и смеяться в голос.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
ОБычно таких вещей элементарно избегаешь из за их сложной читаемости.
Хотя подкол ещё тот, но из разряда «цирка уродов» — прикольно, страшно, но видишь редко.
Интересно, по С++ сдаются какие-нибудь сертификационные экзамены? Ну, это там где нужно наизусть заучивать приоритеты операторов или все возможные способы употребления массива. Интересно как подобное сдается применительно к С++.
В принципе «ошибку» заметил практически сразу после прочтения кода, но тут я ждал нечто такое, иначе зачем топик? В реальной же ситуации, думаю, прищлось бы искать ошибку, возможно долго и мучительно.
Скажите, я правильно пониманию, что эта «фича» стала следствием того, что во-первых, лишние скобки игнорируются (это в статье), плюс внутри реализаций функций можно объявлять внутренние составные типы (ну классы и структуры к примеру), а вот объявление функции просто «попало под раздачу» как ещё один тип?
Не, это следствие того, что компилятор пытается разобраться по-простому, и самое простое, что у него выходит — это прототип функции. Ну а варнинги — подумаешь, из проектов на с++ их эшелонами вывозить можно.
Я думаю всё же, что если б по стандарту было б запрещено объявлять новые сложные типы внутри реализаций, то повода для этой статьи не было, поскольку бы не пришлось из двух возможных «вы имели ввиду» выбирать ту, которая проще (ну или ещё по какому критерию), а осталось бы одно чёткое понимание того, что имел ввиду разработчик.
А где там объявляется «новый сложный тип», поясните — где и какой тип?
Раз такая пьянка, то давайте договоримся об определениях (сразу извиняюсь: повода считать выс тупым или ещё каким нет, просто обсуждение дальше может не пойти в конструктивном ключе, если сразу не договориться):

Тип под типом понимается некая сущность, которая описывает «будущую» объявленную переменную.

Сложный тип — ну может слово «сложный» не совсем уместно, может лучше «ненативный», что переводится как «неродной», т.е. не зашитый непосредственно в язык, т.е. тот тип, который описывает пользователь: сюда относятся классы, структуры, а также объявления функций.

А теперь ответ на вопрос:
B b(A(i)); // вот оно объявление сложного типа

или, как уже было сказано, после убирания лишних скобок:

B b(A i); // вот оно объявление сложного типа

Т.е. в данном случае объявляется некая функция, которая правда ничего не делает, потому что у неё тела нет, да и вообще не понятно как её использовать, поскольку по её вызову ничего не выполняется

В С/С++ чаще используется указатели на функции, а именно было:

B (*b)(A i), которому при предварительной реализации некой функции B ZZZ(A a), можно было ю написать b = ZZZ, а потом вызвать что-то типа b(A a);

Примеры:
#include «iostream»
#include «typeinfo»

struct A {
A (double i) { std::cout << 'A' << '\n';}
};

struct B {
B (A a) { std::cout << 'B' << '\n';}
};

B ZZZ( A a ) { return B(A(1)); };

int main () {
int i = 2;

B b(A(i));

b(A(i)); // (2)

return 0;
}
(сразу извините за #include «iostream» — просто хабраредактор такое выдал при испльзовании больше/меньше) Этот пример не будет работать: ошибка линковки, поскольку в (2) какбэ вызывается обработчик, который не имеет реализации (тела). Но это легко поправить например так:
#include «iostream»
#include «typeinfo»

struct A {
A (double i) { std::cout << 'A' << '\n';}
};

struct B {
B (A a) { std::cout << 'B' << '\n';}
};

B ZZZ( A a ) { return B(A(1)); };

int main () {
int i = 2;

B (*b)(A(i)) = ZZZ;

b(A(i)); // (2)

return 0;
}
Тут всё корректно работает, поскольку мы объявили указатель на функцию и инициализировали его передав ему указатель на ZZZ (аналогия double — тип, double* — указатель на переменную double; B ()(A a) — функция, B (*)(A a) — указатель на функцию).

Так что вот такие пироги :)
Кстати, кто-нибудь знает хоть одно применение вот таких функций-пустышек, которые объявляются внутри функций [ см. B b(A i) ] кроме засорения кода?
Это прототипы функций (forward declaration), которые будут объявлены позже. Они введены для того, чтобы исключить циклические зависимости. В стандарте это _есть_.
Это прототипы функций (forward declaration), которые будут объявлены позже.
Приведите небольшой работающий пример использования таких прототипов и как они будут «объявлены позже». Вот мне не понятно где это они будут объявлены если область действия b ограничена функцией main(), а насколько мне известно, функции внутри функций делать нельзя, можно только выдавать вот такие прототипы.
Да легко (пояснение: A, B — некоторые классы\структуры):

void someFunc()
{
    B b(A a); // если закомментировать эту строку, не будет линковаться

    A a;
    b(a);
}

B b(A a)
{
    // do something and return B
}
Действительно работает! Тока надо было реализацию функции за пределы той функции, в которой происходит задание прототипа B b(A a); вытянуть и я бы тогда сам разобрался (это я себе всё говорил). Всё теперь понял, спасибо. Просто завис на том, что объявление происходит внутри области видимости некой функции, а внутри функций писать реализацию других функций незя. Огромное спасибо ещё раз. Пересмотрю некоторое своё видение в данном вопросе, хотя такой изврат навряд ли применять когда-нить буду.
Это не изврат, это нормальный способ разрешения зависимостей (нормальный для с++) :) В принципе, прототип можно и из функции вытащить наверх, но зачем, если нужно сослаться только тут. В этом подходе функции аналогичны неймспейсам в плане ограничения видимости прототипов/символов.
Очередное спасибо. Этим коментом вы ответили на мой другой вопрос (если конечно теперь немного перефразировать с учётом полученных новых знаний).
Совершенно не за что. Я на сях давно уже не пишу, еще с тех пор, как boost не имел отношения к stl вообще, но базу помню, так что спрашивайте, ежели чего.
По поводу применения — большинство сишных хедеров состоят как раз из таких прототипов.
Кажется, в старые юниксовские времена, так даже было принято писать… Во всяком случае, я видел такие исходники, там, вместо того чтобы сделать обычный #include «header», они прямо перед использованием функции её объявляют, и тут же дёргают :). Вроде такого:
    ...
    int foo(int a);
    int res = foo(10);
    ...
}
>> Тип под типом понимается некая сущность, которая описывает «будущую» объявленную переменную.

Нихрена себе. Так тип — это тип или будущая переменная? Мне всегда казалось, что по отношению к языку С/С++ слово «тип» имеет вполне определенный смысл, и не надо его переопределять.

>> B b(A(i)); // вот оно объявление сложного типа
Это НЕ объявление сложного типа. Тут, правда, можно придраться к слову «объявление» — definition .vs. declaration, но не суть :) Это — прототип конкретной функции. Не типа, а конкретной сущности, с привязкой по ее имени и сигнатуре. «Типами» и не пахнет :)
Нихрена себе. Так тип — это тип или будущая переменная?
Тип — это то, что описывает переменную, но ещё не переменная, в общем случае, по типу нельзя обратиться к чему либо внутри него (static и прочие подобные вещи не в счёт) и вообще что-то с ним делать — приходится создавать переменную или выделять память через new и пр.

Зачем я ввел вначале определения? По опыту общения с разными разработчиками, которые работали с разными языками, знаю, что порой они переносят определения похожих понятий из одного языка в другой и порой происходит недопонимание, когда кто-то использует слово, подразумевая некий другой смысл (опять же может и похожий, но черти, как известно, в деталях). Так один из разработчиков на дух не переносил называние класса или структуры типом — под типами он всегда подразумевал только простые, зашитые непосредственно в язык (double, int и пр.).

B b(A(i)); // вот оно объявление сложного типа
Эта запись аналог например такой:
class
{
public:
double ttt;
} m;
Т.е. в данном случае происходит описание некоего неименнованого типа и происходит сразу создание переменной m этого неименованого типа. Для функции просто работает аналогия. Если неправ — тыкните в какое-нибудь описание или сами что-нить отпишите.
Насчет типа понял, ок. Но позволю себе поправить вас — никаких переменных там не создается. Ссылка тут: habrahabr.ru/blogs/cpp/68796/#comment_1954106 и тут: habrahabr.ru/blogs/cpp/68796/#comment_1953851

Как уже сказал, никакая переменная не создается. Здесь объявляется прототип функции — т.е., символ и сигнатура, что такая функция существует, и ее тело будет определено позже в коде / в другом файле. Это необходимо, потому что с/с++ — однопроходный компилятор, и результат компиляции будет зависеть от того, в каком порядке расположены объекты в файле (модуле в терминологии с++). Чтобы иметь возможность обращаться к тем сущностям, которые описаны позже в файле, и введены forward declarations для функций и пользовательских типов (т.е. можно написать так: struct SomeName, использовать ее, а саму структуру описать в конце файла)
>> Эта запись аналог например такой:
Ну вы уже поняли, что не аналог.
спасибо, разобрался. Ваш коммент выше внёс ясность, теперь есть пища для размышлений, новых проб и ошибок :)
если бы все в мире было по стандарту)
-Wall.
Визуал хитрый:
warning C4930: 'B b(A)': prototyped function not called (was a variable definition intended?)

Хотя он порой такое компилит, что волосы дыбом становятся — как у него это вышло.
Какой уровень надо включить, чтобы этот ворнинг появился? А то на /W4 у VC начинается совершенно параноидальное поведение и он начинает материться на стандартные идиомы. У меня сейчас под рукой нет вижуала, но некоторые перлы помню:

while(true) { blablabla… break… blablabla } // Ругается на константное выражение в while

char x='a';
char z=toupper(x); // Ругается на присвоение int'а char'у

В общем, когда количество выключенных ворнингов в проекте подходит к десятку, начинаешь сомневаться в адекватности мелкомягких.
НЛО прилетело и опубликовало эту надпись здесь
/W3 — по умолчанию стоит, с ним так и матерится
С каких это пор стандарт начал разрешать вложенные определения функций? Вы бы хоть версии компиляторов называли, когда такое постите…
Извиняюсь, версию вижу в тексте.
Это не вложенное определение, а объявление внешней функции. Например, можно написать в одном модуле

int foo() { return 42; }

а в другом

int bar(int x) {
int foo();
return foo()+x;
}

Есть везде, начиная с древнейших диалектов C.
Эх. Не знаю как кому, а мне статья самомнение повысила ))) автор, спасибо. Немного радости перед сном не помешает.
НЛО прилетело и опубликовало эту надпись здесь
C++FQA — очень странный и предвзятый ресурс, я бы назвал его карманным справочником по троллингу для C++ненавистников. Хотя местами комментарии неплохо и по делу дополняют FAQ.

Ну а статью в хсакепе, в которой аффтар не видит разницы между C и C++ и не понимает, зачем может понадобится портабельный ассемблер, даже местная публика заминусовала ниже плинтуса.
НЛО прилетело и опубликовало эту надпись здесь
Страуструп говорил: есть два вида языков — те, которые все ругают, и те, на которых никто не программирует.

Как большой любитель С++ я вижу, что его многие недостатки вызываны объективными факторами, а при умелом использовании языка попросту не видны.

Если даже взять пример из данного поста — я никогда не пишу B b(A(i)), всегда только B b = A(i). Привычка. Если бы у автора поста была бы та же привычка, он бы на грабли не наткнулся.
НЛО прилетело и опубликовало эту надпись здесь
Он не «сделан так», он «вырулил туда». Все языки эволюционируют в сторону сложности и расширения синтаксиса, при этом неизбежно появляются какие-то «паразиты». Сравните, к примеру, первые и последние версии Бейсика, Джавы, Сишарпа.
С++ в современном виде действительно во многом сделан странной политикой комитета в 1990-е, когда в стандарт вносились совершенно необкатанные (а иногда даже и ни разу не реализованные) фичи. В результате получились страннейшие сайд-эффекты, например, подъязык темплейтов случайно оказался полным по Тьюрингу — полезнейшая вещь, но если бы его проектировали сейчас, не пришлось бы так много делать через задницу. А export так никто и не научился поддерживать, кроме довольно маргинального Comeau.

Мне как-то больше нравится более консервативный подход к стандартизации, практикуемый, например, при принятии RFC. Чтобы что-то хотя бы начали рассматривать как кандидата на включение в стандарт, необходимо предъявить работоспособную имплементацию.
Возможно, хотя стандартизация — штука тонкая, вероятно, здесь нет «лучшего» решения. В качестве отрицательного примера можно привести сверхконсервативную стандартизацию языка Ада. Сначала была Ада 83, потом Ада 95, в которой появились какие-то классы и вообще претензия на ООП. Господа, повторюсь, в 1995 году! (Шаблоны, надо признать, там в некотором виде уже имеются).
Это вы ещё перл не пробовали. Там основной принцип — There's more than one way to do it
Да, надо ругать создателей грабель за то что на них наступаешь. И рыхлить землю руками…
НЛО прилетело и опубликовало эту надпись здесь
Вы рыхлите землю лопатой? O_O
НЛО прилетело и опубликовало эту надпись здесь
Это всё конечно хорошо :), но какова замена? На чём другом писать высокопроизводительный софт?

Критиковать все горазды…
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
потому что это простой и удобный способ не вводить дополнительную переменную A, если она больше никогда в жизни не пригодится. Точнее, был бы простым и удобным, если бы оно работало. А от лишних скобок это выглядит уродливо.
НЛО прилетело и опубликовало эту надпись здесь
«Читабельность кода имеет куда более высокий приоритет. „
Ну так я о том же ))
Например, API требует передать в конструктор какой-нибудь объект типа A. Вы понятия не имеете, что такое этот тип A, нафиг он нужен конструктору, и уж точно уверены что сами никогда его использовать не будете. Так можно было бы загнать этот A прямо в вызов, и не помещать в код лишнюю мусорную переменную. Если таких вызовов конструкторов будет много (например, при реализации listener'ов для GUI), то в коде могут оказаться тонны мусорных переменных.
м?
НЛО прилетело и опубликовало эту надпись здесь
Ужас!

Кстати, студия (2008) на такой код даёт вполне вменяемый ворнинг: «warning C4930: 'B b(A)': prototyped function not called (was a variable definition intended?)»
Лучший вариант всё же:
A a(i);
B b(a);
Дабы избегать такой «магии» да и выделять одному действию одну строку. Иногда даже в code conventions пишут такое. В action script встречал что-то похожее: нужное мне действие корректно выполнялось точль после введения дополнительной переменной.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории