Sunteți pe pagina 1din 4

C++criticismbyotherpeople

PartofC++FQALite

ThispageisacollectionofthebestC++criticismbyFQAreaders,copiedfromemailmessagesandonlinediscussions.Ifyouknowaninteresting
consequenceofC++problemsnotmentionedintheFQA,pleasesendmeemail.SimilarlytotheFQAerrorspage,thisoneliststhingsthatcanbe
proved/testedratherthanqualitativestatements.Thestuffispublishedwithcreditsoranonymously,accordingtothechoiceofeachauthor.

Theissueslistedhere(ortheFQAitself)arenotsupposedtobe"new"inthesensethattheywereneverdiscussedinapublishedwork(ifC++
problemsweresohardtodiscoverastotakedecades,discussingthemwouldn'tnecessarilybeworththetrouble).

There'slotsof(wellreasonedorentertainingorboth)C++criticismontheweb,includingseveralpiecesbycelebrityprogrammers.However,Imade
thedecisionnottocitefamousquotesbycelebritiesonthissite.ThemainreasonisthatIdon'twanttomaketheFQAmoreconvincingtothepeople
whomostlyvaluethecredentialsofanauthorandignorethingslikefactsandreasoning.IwanttoworklessbothwithC++andwiththesepeople.So
I'dratherhavethemuseC++thanconvincethemtoswitchtosomethingelse.

Implicittypeconversions
C++grammar:thetypenamevsobjectnameissue
C++grammar:typevsobjectandtemplatespecializations
printf,iostreamandinternationalization
Staticbindingrules

Implicittypeconversions
Anonymous:I'daddsomethingaboutthebrokentypesystemhowthefollowingcodeislegalandcompileswithoutwarningswithmostcompilers,for
example:
void foo(const std::string &) {}
int main()
{
foo(false);
}

Anotherexample:
class A { public: int a; };
class B : public A { public: int b; };
int main()
{
A * p = new B[10];
p[5].a = 1;
}

Whyshouldastatictypesystemallowthiswithoutanexplicitcast?

Yossi:TheFQAdoesn'ttalkmuchaboutimplicittypeconversions,sincetheFAQdoesn't.Theproblemisquiteimportantthough.Itwouldn'tbesobad
ifC++detectedruntimeerrors(asopposedtocompilingthesecondexampletocodemodifyingthewrongplace),and/orifsomanyC++
programmersdidn'tthinkthat"withC++,whenitcompilesandlinks,itwillruncorrectly"(Iactuallyheardthisone,andthentherearemanylargeC++
monolithicapplicationswithoutunittestsspeakingforthemselves).

NotethatthishasnothingtodowithsafetyandC++beinga"powertoolallowingyoutododangerousthings"becauseit'sso"highperformance".
Thisargumentonlymakessenseforexplicitcasts.Whatthecodedemonstratesisunexpectedinteractionsbetweenpairsofdifferentimplicit
conversions(inthefirstexample,bool -> char* -> std::string,inthesecondB[] -> B* -> A*).

Bytheway,thesecondexampleexplainstheFAQ'sremarkaboutarraysbeingevilinthecontextofinheritanceandsubstitutability.Thethingisthat
witharraysofobjects,there'sanimplicittypeconversionthatallowsyoutoviolatethesubstitutabilityprinciplewithoutacompiletimeerror.With
std::vector<B>,there'snoimplicitcast.Ididn'tunderstandtheFAQwastalkingaboutthat,becausemostofthetime,wheninheritanceand
polymorphismareinvolved,youallocatearraysofpointerstoobjects.Andinthatcase,there'snodifferencebetweenavector<B*>andaB**you'd
needanexplicitcastinbothcases.Iautomaticallythoughtthequestionwasthecontinuationofthediscussioninprecedingquestionsaboutwhythe
compilerwouldn'tdothecastimplicitly,andinthatcontextthe"arraysareevil"remarkdidn'tquitefit.Icompletelyforgotaboutthearraysofobjects
case,whichissomethinganewbiecomingfromanotherlanguage(andwithC++,somepeoplestay"newbies"foryears)couldverywelltrytouse.

C++grammar:thetypenamevsobjectnameissue
drorz:InC/C++youcannotseparateparsingintoseparatesyntaxandsemanticpasses.Noexistingcompilerdoesitintwoseparatepasses.

Intheexample:
AA BB(CC);

Theparsetreeisdifferentinthefollowingcases:

WhenAAandCCaretypes,BBisafunctionprototype.
WhenAAisatypeandCCisavariable,thenBBisavariable/objectinitialization.
WhenAAisavariable,AA BB(CC)isillegalanditsparsetreeisentirelydifferentfromthefirsttwocases.

Youcannot(moreprecisely,noonediditinarealC/C++compiler)fixawrongparsetreeinsemanticanalysispass.

Considerthisexample:
x * y(z);

intwodifferentcontexts:
int main() {
int x, y(int), z;
x * y(z);
}
and
int main() {
struct x { x(int) {} } *z;
x * y(z);
}

Inthefirstcasex * y(z)isexpression,andinthesecondcaseitisadeclarationofpointery.Parsetreesforthosecasesarecompletelydifferent.

Yossi:ThisisthefirstpartoftheproblemmakingtheC++grammarundecidable.ThesecondpartoftheproblemisthatAAmayreallybe
Template<Params>::InnerDef,andfiguringoutwhetherInnerDefisatypenameoranobjectnameisequivalenttosolvingthehaltingproblem,since
templatesmayinstantiatethemselvesrecursivelyandinfactrepresentarbitraryrecursivefunctions.MaybeI'llexpandonthisonelater.Inparticular,it
hastodowithtemplatespecializations,whicharediscussedinthenextitem.

Puristswhodon'tlikethe"nearlycontextfree"expressioninDefectiveC++:whenyouwriteparsers,itdoesmakesensetodiscussthe"extent"of
yourdependenceonthecontext.Forexample,C++inheritsthetypename/objectnameriddlefromC.ButinC,youcansolveitusingasingle
dictionaryoftypedefnames.Ofcourse,theoreticallytheimportantpartisthattheCgrammarisdecidable(thoughnotcontextfree).Inpractice,what
mattersisthatit'seasytoparse.Inparticular,youcanuseaparsergeneratorforcontextfreegrammars(yacc/bisonisonematureprograminthis
family)withthesimple"symboltablehack"describedabove,andgetaworkingparser.Thisiswhat"nearlycontextfree"means.

Ithinktheexampleisexcellentsinceittookmequitesometimetofigureoutwhatthecodemeansmyself(Ithinkit'stheasterisk).TheAA BB(CC);
exampleusedinDefectiveC++issimpler,butIthinkitdoesn'tconveythepointasclearly,sinceapparentlyitmakesitintuitivelyeasiertocounterwith
somethinglike"youcansolvetheambiguityatthesemanticalanalysisstage".Notethatyoucanalwayscounterwiththatforexample,youcansay
thatthe"parsetree"ofyourlanguageissimplythelistofcharactersinthefile,andtherestissemanticalanalysis.

C++grammar:typevsobjectandtemplatespecializations
drorz:Considerthefollowingexample:

#include <cstdio>
template<int n> struct confusing
{
static int q;
};
template<> struct confusing<1>
{
template<int n>
struct q
{
q(int x)
{
printf("Separated syntax and semantics.\n");
}
operator int () { return 0; }
};
};
char x;
int main()
{
int x = confusing<sizeof(x)>::q < 3 > (2);
return 0;
}

Ifyou"didn'tcare"aboutsemanticsduringparsing,thenconfusing<1>::qisatypename,soconfusing<1>::q<3>(2)createsanobjectoftype
confusing<1>::q<3>withtheargument2.

Ifyou"do"semanticsduringthesyntaxpass,thenconfusing<4>willbelookedup,confusing<4>::qisavariable.Thedeclarationwould"expand"to
int x = (confusing<4>::q < 3) > 2.

Youcanseethatparsetreesinthosecasesarecompletelydifferent,basedontheoutputofthesizeofoperator!

Yossi:...andsizeofdependsontheplatformandtheimplementationdetailsofinheritance(includingmultipleandvirtual),virtualfunctions,etc.The
"parser"getscloserandclosertoafullblowncompiler.

Theproblemisthefreedomthattemplatespecializationshavewhendefiningmembers.Nowifanybodyshowedmeausefulapplicationoftheability
todefinesomethingasaninnertypeinonespecializationandastaticvariableinsomeotherspecialization,I'dbesurprised.

TheredditthreadwhichthisandthepreviousexamplearetakenfromhasadetaileddiscussionaboutparsingC++.

printf,iostreamandinternationalization
AlexanderE.Patrakov(patrakovatumsdotusudotru):TheFQAlistsvalidinformationforandagainsttheuseof<iostream>insteadof<cstdio>.
Thereis,however,onemorethingfor<cstdio>andagainst<iostream>:thepossibilitytotranslateprogrammessagestoadifferentnaturallanguage
(using,e.g.,gettext).AndhereIdon'tmeanthatthereiscurrentlynogettextequivalentforC++iostreams,butthatthereisnowaytodesignsuch
thingcorrectly.

Translationworksonphrases,notontheirparts.Consider,e.g.,suchCstatements:
printf("Read %d files\n", total);
printf("New data were found in %d files\n", found);

WiththestandardC++iostreams,thisbecomes:
cout << "Read " << total << " files\n";
cout << "New data were found in " << found << " files\n";

Awelldesignedprogramfetchestranslationsfromamessagecatalog,Windowsresourceoranywhereelseexceptitsownsourcecode.WithCand
gettextmessagecatalogs,thetranslatorseesthewholephrasessuchas"Read%dfiles","Newdatawerefoundin%dfiles",etc.Ifthesame
approachwereappliedtoC++,thetranslatorwouldseejust"Read","Newdatawerefoundin",and"files"(usedtwice).Lackofcontextistheleastof
allworries.Therealproblemisthat,e.g.,whentranslatingtoRussian,thetwoinstancesof"files"havetobetranslatedslightlydifferently,because
Russianhassixgrammaticalcasesanddifferentcasesarerequiredinthetwosentences:
Read %d files => %d
New data were found in %d files => %d

(approximatelyIdon'twanttooverwhelmtheexamplewiththesingular/pluraltreatment)

Evenworse,examplesexistwithtwoformatsubstitutionswheretheyhavetobereorderedwhentranslating.C(or,moreprecisely,theSingleUNIX
Specification)allowssuchreorderingwithsomethinglikeprintf("%2$d x %1$d inches", width, num);butinC++theoutputorderoffieldsishard
coded.

Thedownsideis,ofcourse,thatnobodyexceptthetranslatorchecksthetranslatedformatstring,andwronglycopiedconversionspecifierscancrash
aprograminthecorrespondinglocale(andthisdidhappenwithsedandviminthepast).

SeehowTrolltechhandlestheabovementionedproblemsintheirQttoolkit.

Yossi:Ireallylikethisexamplebecauseitcanbearealeyeopenerforapracticalprogrammer,andIwishIheardandthoughtaboutitseveralyears
ago.Clearlytheprintfinterfacegetssomethingrightthatiostreamdoesn't,sinceitseemstosaveuslotsoftrouble.Whatisitthatprintfgetsright?
Coulditbethatrepresentingtheprogramstructureusingcompiletimeconstructsincomprehensibletoanytoolexceptforthecompilerisnottheway
togo?Effectivelytheadvantageoftheprintfprogramisthatit'seasierforotherprogramstomanipulate.Theideathatbackfiresisthatprogram
structuremaybeencodedinabitrarilycomplexwaysandtheonlyonewhoeverhastoworryaboutitisthecompilerwriter.

Butmaybethistranslationbusinessisasingularityinthecomputinguniverse,andweshouldn'tinfergeneralconclusionsfromit?Well,here'sanother
example.Supposeyouwanttodorealtimelogging.Youdon'thaveenoughtimeand/orbandwidthtodotheformattingatthetargetmachine.Andyet
youwanttologfreetext,notsomestrictbinaryformatwithversioningschemesandfixedsizelimitsandotherheadaches.Withprintfstyleinterface,
youcanlogpacketsof(forexample)32bitwordssize,constantformatstringpointer,andthelistofarguments.Youcanthenextracttheformat
stringsfromtheexecutablefile(readingELForCOFFfilesiseasythereareexamplesonthenetofabout200linesofCcode),anddothe
formattingatthehostmachine.Now,withiostreamlikeinterface,theformatstringissplittomanylittleparts,andallkindsoftypescomeinthemiddle
typesofdataitemshavetobeencodedintheloggedpackets,too.Andyou'dhavetologcallstoI/Omanipulatorssuchashex,setfill,etc.Clearly
theoverheadperloggeddatawordisgoingtoincreasesignificantly.

Thinkaboutit:howcanitbethatasimplistic"1formatstringplusNargumentsofdynamictypes"interfacebeatsanadvanced"staticallydispatched
polymorphicoperators"interface,andwhatmakesitsurprisingtoyou?

Staticbindingrules
MiguelCatalina:Thefollowingtestprogramdoesnotcompileundergcc4.3.{1,2}:
#include <cmath>
struct my_class {
my_class(int) {}
};
inline my_class operator&&(my_class,int){return my_class(0);}
int main(void)
{
double x = std::pow(1.0, 1.0);
(void) x; to avoid unused variable warning
}

Theerrormessageis:
$ g++ -Wall -pedantic-errors simple_test.cpp
simple_test.cpp: In function int main():
simple_test.cpp:11: error: ambiguous overload for operator&&' in std::__traitor<std::__is_integer<double>, std::__is_floating<double> >::__value
simple_test.cpp:11: note: candidates are: operator&&(bool, bool) <built-in>
simple_test.cpp:7: note: my_class operator&&(my_class, int)

Thereasonforthiserrorhastodowiththisdeclarationincmath:
template<typename _Tp, typename _Up>
inline
typename __gnu_cxx::__promote_2<
typename __gnu_cxx::__enable_if<__is_arithmetic<_Tp>::__value
&& __is_arithmetic<_Up>::__value,
_Tp>::__type, _Up>::__type
pow(_Tp __x, _Up __y)
{
typedef typename __gnu_cxx::__promote_2<_Tp, _Up>::__type __type;
return pow(__type(__x), __type(__y));
}

Tracingafewheaderfilesupbringsustobits/cpp_type_traits.h:
template<typename _Tp>
struct __is_arithmetic
: public __traitor<__is_integer<_Tp>, __is_floating<_Tp> >
{ };

...and:
template<class _Sp, class _Tp>
struct __traitor
{
enum { __value = bool(_Sp::__value) bool(_Tp::__value) };
typedef typename __truth_type<__value>::__type __type;
};

Soitturnsoutthattheoperationthatisgivingustroubleisthe&&insidethe__enable_ifinthetemplatedeclarationofpow().Weareinvoking&&
withtwoenumoperands(__is_arithmetic<T>::__valueisanunnamedenum).Iguessthecompileristreatingunnamedenumsasints.Sothecompiler
istryingtocalloperator&&(int,int).Butthereisn't,thereareonlyoperator&&(my_class,int)andoperator&&(bool,bool).Sothecompileristryingto
doanimplicitconversionoftheoperandssothattheycanmatchtheavailableprototypes.Thereareimplicitwaysofconvertinganinttoamy_class,
aswellasconveringaninttoabool.Thecompilerdoesnotknowwhichonetouse,hencetheambiguity.

Thequestionis:whyonEarthwhenyouaretryingtoinvokeafunctionthatonlydealswithdoubles,doyouhavetodealwiththeambiguitybetween
twoavailableimplicitconversionsfortypesthathavenothingtodowithdouble?
Yossi:Takestimetowrapone'smindaroundthis,um,treason(don'tyoujustlovethepublic __traitorbit?Iguessa"traitor"issomethingusedto
generatesocalled"typetraits",akeyidiomintheworldofC++templatesarcana).Nowthatwe(presumably)understandtheerrormessage,how
wouldyouworkaroundtheproblem?Ifthecompilerwouldbarftryingtodispatchanoperatorwithuserdefinedtypes,wecouldspecificallydefinethe
operatorwiththeprototypeitwouldpickasthebestmatch(astheGNUSTLimplementorsthemselvesdoinsimilarsituations).Butwecan'tdefine
operator&&(int,int).Nowwhat?

Copyright20072009YossiKreinin
revised 17 October 2009

S-ar putea să vă placă și