Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
// a.h
class A
{
public:
void foo(int n);
void bar();
void baz();
};
// a.cpp
#include "a.h"
void A::bar()
{
foo(); // ошибка
}
void A::foo(int n = 0)
{
if (n)
foo(); // OK
}
void A::baz()
{
foo(); // OK
}
// b.cpp
#include "a.h"
int main()
{
A a;
a.foo(); // ошибка
}
#! /usr/bin/env python3.0
class A:
def foo(self, n=10):
print("A.foo, n = ", n)
class B(A):
def foo(self, n=20):
print("B.foo, n = ", n)
pa = B()
if isinstance(pa, A): # true
pa.foo() # B.foo, n = 20if (x = 0) {B:foo).Если вы ответили A::Foo, n = 10, скорее всего, вы новичок в С++.Охх… если бы! :-)
получаются логически разные вещи по вашему, хотя это одно и тоже исходя из логики языка с++Вы идиот или притворяетесь? pa->Foo() — это вызов функции Foo(), (pa->*(&A::Foo))() — это синтаксическая ошибка. Вы не можете в этом примера написать что-нибудь типа:
вызвать функцию 'Foo' класса 'A' для представителя этого класса доступного по указателю pa с параметрами по умолчанию.Этого там не написано. Там написано: выяснить как реализована функция Foo для класса A или его потомка, на экземпляр которого указывает pa, и вызвать эту реализацию.
Заметте в строчке не написано «вызвать функцию Foo класса B» или какого другого класса, откуда компилятор должен решать что надо вызывать с параметрами по умолчанию для B?А как он это делает с самой функцией Foo? Почему компилятор достаточно умён чтобы извлечь из указателя pa информацию про нужный ему вариант функции Foo и недостаточно умён для того, чтобы извлечь из того же указателя информацию о правильном умолчальном параметре?
эээ и как это компилятор может выяснить какой именно потомок будет по указателю pa в общем случае? :)))Компилятор ничего и не выясняет. Его цель — скомпилировать код, который сделает то, что я сказал.
налицо непонимание процесса, компилятор не извлекает ничего из указателя вообще.Налицо желание притвориться идиотом.
Это делается самой программой в процессе исполнения.Правильно — но делает-то это код, сгенерированный компилятором!
В общем случае мы вообще не можем сказать какой именно потомок класса будет доступен по указателю во время работы программы. Может C, может D может E, может какойто мультипотомок.Правильно — но если мы не можем сказать что за функция будет вызвана, то почему вдруг мы должны мочь сказать какой именно «умолчальный параметр» будет тут использован? Какие великие истины рухнут если это знание также будет выявляться во время исполнения?
При этом вы пишите код видаА как вы можете вообще быть в чём-то в этом случае уверены? У вас должно быть описание библиотеки и там это будет описано. Более того, на практике часто так и делается: есть тупо две функции DoSomething() и DoSomething(Options o) и дефолтное значение опций вам даже не показывают. В случае виртуальных дефолтных параметров это была бы одна функция, а не две…
pa->Foo()
как вы можете быть уверены что при этом в Foo передается именно то что вы имелли ввиду?
Лично я с семантикой: что написано в интерфейсе который я вызваю, то и используется могу быть абсолютно уверен, что параметром по умолчанию будет именно то что написано в описании класса на котороый ссылается pa (в данном случае A).И что — от этого знания на вас снисходит космическая благодать или как? Это же значение потом немедленно поступает в функцию, которая делает что-то, над чем вы контроля не имеете!!! Ну и что вам это пустое знание даст?
одна версия хуже другой, потомучто допускает неоднозначные трактовки в реалиях с++Нифига подобного. В книжке Страуструпа прямо предлагалось трактовать дефолтные параметры как создание функций с n-1, n-2 и т.д. параметрами, которые вызывают фанкцию с n параметрами. Никакой беды от того, что эти воображаемые функции будут объявлены виртуальными не случится.
вспомните что есть ещё указатели на функции члены (собственно первое мое объяснение уже должно вызывать у Вас вопросы по поводу предлагаемой Вами схемы), множественное наследование, дочернее наследование, обрезание классов при присваивании не указателями и тп.И что? В каком месте описанное преобразование вступает с этими правилами в противоречие?
компилятор в этой строчке видит именно вызов функции A::Foo, а не B::Foo
почему он должен брать его из B?А почему бы и нет?
Но откуда компилятору это знать?А откуда он знает что ему нужно вызывать B::Foo на самом деле? Из VTable. Если компилятор туда смотреть не будет — никакая виртуальность не заработает… Оттуда же можно было бы и про параметры узнать.
так написано то вызвать A::Foo :-)А вызвалась B::Foo! Куда файлить баг?
а потому что это не то поведение которое можно было бы ожидать от вызоваА почему нет?
A::Foo с параметрами по умолчанию (ведь как описано выше, вызывается именно она)Выше написана чушь ибо вызывается не A::Foo, а B::Foo.
компилятор не знает что ему надо вызывать B::Foo :)))) в строчке pa->Foo()Но он её как-то вызывает? Чудеса в решете.
когда программа дошла в процессе выполнения до этой строчки, так получилось что в pa оказался указатель на представитель класса B, взяв по типу класса VTable и получив ссылку на функцию программа доберется до функции B::Foo, которая у нас одна а не две (как вам бы хотелось)И что это доказывает? Что вызов функций у нас виртуальный, а обработка параметров умолчания — невиртуальная? С какого перепугу так получилось и что было бы если бы разработчики чуток побольше подумали над этим феноменом?
полное непонимание процесса, компилятор не вызвает функции вообще, они вызываются самой программой в процессе работы.Полное непонимание обсуждаемой проблемы.
у вас есть код:Вызывается E::Foo — ни о какой виртуальности не может быть и речи. Компилятор достаточно умён для того, чтобы просечь подобные вещи. А вот если вы поместите эти две строчки в разные функции — тогда вопрос становится интересным и в точке вызова e->Foo() придётся вставлять код, который выяснит — какую же именно функцию нужно вызвать.
E *e = create_some();
e->Foo()
какой метод вызывается в e->Foo()?
и что такого? в с++ много чего есть невиртуального, и что? Тот же полиморфизм в с++ двоякий, как времени исполнения (виртуальность) так и шаблоны — времени компиляции. Вас это пугает?Нет. Я просто хочу сказать что принятое решение создало крайне кривой и неудобный язык: нужно было либо запретить определение параметров умолчания в потомках, либо сделать чтобы и параметры вызывались тоже виртуально. То, что этого не сделали — косяк, каких в C++ много.
Опять же выше приводили пример с питоном, в котором поведение точно такоеже, считаете что и там дизайнеры языка ошиблись?Нет, там дизайнеры сделали всё правильно, выводится
B.foo, n=20Вcё «как у людей».
нет, это вы пытаетесь объяснить что если сделать так как вы хотите то всем будет хорошо. Все объясняют вам что это не будет хорошо.Не вижу объяснений. Вижу всё более истеричные вопли с голословными заявлениями о том, что «будет плохо», «ой плооохо будет », «НУ ВАЩЕ — Я ЖЕ СКАЗАЛ ЧТО БУДЕТ ХРЕНОВО».
Как можно быть уверенным что передается туда по умолчанию с реализацией «отдельный потомок, отдельный параметр по умолчнанию»?А зачем мне знать что туда передаётся именно этот параметр? На кой ляд? Что я получу от этого знания? В любом случае понять что эта функция делает я смогу только прочитав документацию.
опять же проблема реализации, в описанном вами примере вы зачемто перекладываете определение кисти на сам класс объекта.Это логикой задачи определяется. Округлые линии лучше рисовать мягкой кистью, а треугольники и квадраты — жётской.
Лушее решение в данном случае — чтобы был некоторый класс предоставляющий интрефейс выбора кисти, параметром которому был бы контекст.Класс, разумеется, есть. И умолчальным значением там стоит NULL. А если там NULL — то объект выбирает самую правильную кисть.
Для сомневающихся — §8.3.6, пункт 10 в текущем working paper стандарта ISO/IEC 14882: Programming Language C++. (Ссылка на working paper дана просто по причине его бесплатной доступности. В действующем стандарте 1998 года этот пункт находится в той же редакции.)
называть какойто язык кривым в силу непонимания — по моему неверноЯ-то как раз прекрасно понимаю почему сделанно именно так или иначе. И зная историю C++ я понимаю что дефолтные параметры обрабатываются именно так как они обрабатываются не потому что так логичнее и удобнее, а потому что разработчикам первого компилятора так было удобнее его писать.
программисту не неважно, программист экономит своё время печати :)Почему вы решили что это единственный способ использования параметров по умолчанию? Потому что в C++ они больше ни на что не годятся?
А параметры по умолчанию, они именно для этого: для короткой записи :)Это единственное для чего они годятся в сегодняшнем C++ — но почему это должно быть единственным что с ними можно сделать вообще?
Как вы будите воспринимать эту функцию?Как функцию, которая принимает либо один аргумент, либо два. Не вижу тут ничего сложного.
Ну тут уже я не знаю как еще можно понимать. Вродибы есть интерфейс, вродибы в нем черным побелому написано что дефолтное значение =10, вродибы все однозначно.С какого перепугу это дефолное значение вообще является частью интерфейса? Потому что так в C++ сделано? Плохо сделано. Переделывать уже поздно, но это не значит что нельзя было сделать лучше.
странно, вообщето с++ не меняется года этак с 97го :) в 2003 году стандарт обновляли, но там фактически только стандартную библиотеку расширили, никаких глобальных переделок :) такчто язык строен уже давным давно :-) а на дворе 2009йВообще-то GCC с версии 4.3 поддерживает ключи -std=c++0x и -std=gnu++0x… Мне казалось вы о таких вещах как концепции (concepts), std::thread, auto и decltype… Всё это, конечно, хорошо, но настолько мало — за 10-то лет работы!
Тренды — это то что народ наконец то начал вытворять на этом языке, когда появились компиляторы реализующие почти 100% его функциональности.В основном я вижу вещи, которые со страшным скрипом и ограничениями реализуют то, что на других языках делается легко и просто. И вызывает это скорее не восхищение, а жалость: ну неужели же нельзя было сделать язык чтобы всякие концепции реализовывались удобнее, чем этот кошмар? Чтобы нём была полноценная рефлексия и нормальные замыкания? Ну зачем людей-то мучить?
Даже то что добавляют в новый стандарт, фактически можно сделать текущими средствами :) (разработчики буста это преркасно показали :)Вот это-то и ужасно. Вместо того, чтобы исправить болячки и добавить вещей, которых внутри самого языка не реализовать в него добавляют какие-то мелкие рюшечки и преподносят это как «грандиозный шаг вперёд»…
операционная система Winodws это как раз тот пример который со скрипом реализуется на других языках?Так точно. Почти все фичи Windows были реализованы на Lisp-компьютерах много-много лет назад. То, что потратив буквально десятки тысяч человеко-лет и миллиарды долларов (то есть раз так в сто больше чем при работе на вменяемых языках) Microsoft смог «взять» эту, не бог весть какую высокую, высоту и превзойти её (уже в этом веке) — чести этому языку не делает. Это скорее доказывает что «и слона можно научить танцевать»…
а народ то и не знает :) :) :) :)Народ вообще много чего не знает.
это вы про лисп? :))Ну разумеется! Когда вы свободно встраиваете любые объекты друг в друга как в Lisp'е — это не настоящий танец. Вот когда у вас куча компиляторов, костылей и сложных правил, работоспособность которых зависит от фазы луны (как в C++), тогда да — это то, что надо.
почитайте страуструпа «дизайн и эволюция языка с++» узнаете хоть как на самом деле оно происходило :)Знаете — это как учебник советской истории. Выделены удачные моменты, убраны неудачные, то что лично Страуструпу не нравится — совсем исключено.
в чём кривость то? в вашем непонимании? в вашем субъективном восприятии? тогда любой язык который вы не понимаете — кривой :)Если бы я не понимал C++ — было бы не так обидно. Но там куча вещей, вызванных якобы стремлением к эффективности, которые потом боком выходят. Например те же невиртальные методы — зачем они? Концепция финальных методов (как в Java) — гораздо безопаснее и практически не менее удобна. Или typeid — это не рефлексия, а чёрт знает что. Ну и куча других моментов.
но вы же сами признались что не понимаете его, сказав что поведение компилятора — кривизна! :-)Маразм какой-то. Вы всеръёз верите в то, что понять что-то == полюбить это? Ну ладно — верьте дальше.
зачем невиртуальные методы?! а зачем вообще нужны методы?! :-) или класс без виртуальности уже не класс? :))Вы даже не вдумались в то, что предлагается в качестве альтернативы — очень характерно для всего спора. Подумайте — чем финальные методы Java отличаются от невиртуальных методов C++, какие ограничения это накладывает и какие преимущества даёт — потом можно будет что-либо обсуждать.
typeid ну тут уже как компилятор реализует :)Да проблема не в том, что он реализует, а в том чего он не рализует. Полноценной рефлексии он не релизует. Очень тяжёлая, сложная в реализации конструкция фактически даёт вам dynamic_cast — и всё.
в 90е годы с++ фактически еще не было, нормальные компиляторы появились этого году после 2000го, а до этого это был «Си с классами», как не прискорбно этого говорить :(Правильно, но в 90е годы язык развивался, становился реально лучше и была надежда что «гадкий утёнок» превратится-таки в лебедя. А утёнок вырос и превратился в гадкого гуся — на чём развитие и застряло. Обидно.
спокойствие только спокойствие, вы говорили про понимание, я указал что вы сами говорили про его не понимание, только и всего, причем тут любовь?Где я сказал что я чего-то не понимаю? Я прекрасно понимаю чем вызвано такое поведение (вначале сделали «как проще», а потом уже поменять что-либо не так просто), но понять и принять — разные вещи.
а вы чего хотите от комипилируемого в машинный код языка? :)Посмотрите на Oberon, на Objective-C, на COM, наконец. Всё это — компилируемые языки. Почему ничего подобного нет в C++?
Реально, всё что в стандарте было придумано страуструпом уже году к 95ому (бюрократия знаете ли), а компиляторы появились только году в 2001 нормальные, чувствуете сколько лет прошло?Чувствую. И в 1995м году стоило бы задуматься: а туда ли мы идём? Но нет — попёрлись через буреломы и тайгу…
очень сильно подозреваю что вы даже не представляете что можно сейчас вытворять на этом языке…Очень хорошо представляю — ибо мне приходится всё это расхлёбывать. И ничего кроме недоумения большая часть этих достижений не вызывает: да, вы смогли наворотить 10'000 строк шаблонов и сделать что-то что можно было бы добавить в язык за неделю работы? Грандиозно — но нафига мне эта демонстрация RFC1925? Видимо глядя на один и тот же код вы вспоминаете первое предложение правила номер 3 и вы восхищаетесь его правдивостью, а я — второе и третье. Истинная мудрость этого правила — таки в третьем предложении, а C++-программистам свойственно его забывать…
Знаете, за то слава богу — это работает и работает отлично в промышленных масштабах.Это такая практическая реализация ранее провозглашённого приниципа
Проще залезти например в пещеру и воспринимать мир в рамках этой пещеры, отбрасывая весь остальной мир как несуществующую реальность…или как?
#include "A.h" // void foo(int x, int y = 10)
void foo(std::vector<A*> const & v)
{
v[0]->foo(5); // Уверен, что 10, это просто сахар
}
программист использующий интерфейс ОЖИДАЕТ ЧТО ЕСЛИ НЕ УКАЗЫВАТЬ НИЧЕГО БУДЕТ ПЕРЕДАВАТЬСЯ результат вызова SomeDefault() или вы воспринимаете это както иначе?Я воспринимаю это как возможность использования функции с передачей параметра и без оной. Конкретное значение этого параметра — такой же факультатив, как и указание названий параметров.
вы предлагаете хранить два (или больше, смотря сколько параметров по умолчанию) указателя в VTable, при компиляции генерировать также различные вызовы вместо одного. Генерировать отдельно столько же функций для всех потомков, ведь это по вашему отдельные функции. и тпДа, но зато мне не нужно генерировать код вызова SomeDefault() в каждой точке, где вызывается A::Foo — если вызовов много, можно получить изрядную экономию…
что делать если наследование вот такое вот, что делать если было обрезание что делать… ну и тп, только на первый взгляд сложности тут есть сразу с множественным наследованиемНет там никаких сложностей. Ну то есть совсем нет. Простое правило тривиальным образом определяет что произойдёт в том или иным случае.
то есть этой функции наплевать на параметр вообще? :) зачем он тогда? :)Ну зачем он ей нужен — конкретная реализация решит. А что он может присутствовать — полезно прописать в интерфейсе. В C++ же нет замыканий...
а куда этот код денется? :) экономии не получится вообще, экономия есть только когда функция одна, как собственно и есть в языке C++Этот код окажется в той точке где реализована функция и если вы вызываете её больше одного раза — будет экономия. Когда же у вас есть «одна функция», то компилятор будет вставлять вызов конструктора во все вызовы функции Foo с дефолтным аргументом! Вот как раз в текущем подходе C++ никакой экономии нет — что функция с дефолтным параметром, что инлайновая функция без параметров плюс функция с параметром — код един.
простое правило? :)))) уже тыщу раз объясняли что это простое правило усложняет и восприятие и реализацию :)Про усложнение реализации — это спорно, но возможно (выигрываем в одном месте, проигрываем в другом), про усложнение восприятия — только в том случае когда у вас мозги изуродованы существующим подходом C++.
большинство профессиональных программистов, именно на этом примере (когда интерфейсное описание пытаются переопределить в потомке) согласятся (и согласились в этом обсуждении) что та реализация что есть лучшая ввиду (повторюсь) простоты, непротиворечивости и невлиянии на интерфейс :)Вы знаете — большинство Россиян согласятся с тем, что выучить русский несомненно проще, чем английский — что, однако не далает этот факт правдой.
class A {
public:
virtual void Foo (int n);
inline void Foo () {
Foo (10);
}
};class A {
public:
virtual void Foo (int n = 10);
};Foo с использованием параметра по умолчаниюA * pa = new A ();
pa->Foo ();A не претерпевает измененийA его «интерфейс»?class A {
public:
virtual void Foo (int n);
void Foo ();
};10 исчезла из «интерфейса» класса без вреда для него. Из чего следует, что она не является его частью. Из чего следует, что она является частью реализации класса A.С++ не имеет? С++ имеет и еще как
С++ это не совсем ООП в чистом виде язык… это мультипарадигменный язык.
A безусловно разные. Интерфейсы — идентичные. Даже несмотря на то, что число публичных методов одинаковое. С точки зрения интерфейса определение реализующего его класса — это уже деталь реализации.Foo (int n = 10) и с двумя Foo (int n) и Foo () обладают идентичными интерфейсами.if (force==10) force=1000;документация по тому что будет если вызвать эту сигнатуруТо есть у вас всё-таки есть не только сигнатура?
Не понял про идиотский подход в С++. Осветите пожалуйста?да уж тут светили, светили — всё без толку. С какого перепугу детали реализации (значения дефолтных аргументов) засунуты в C++ в интерфейс? Кому от этого хорошо?
И той сотне программистов С++ которая сидит в моей конторе.А той тысяче что сидит у нас — плохо и потому дефолтные аргументы запрещены. Теперь будем считать сколько у кого программистов?
return 0; то и delete pa; не грех написать.
Неожиданное коварство параметров по умолчанию или язык мой — враг мой