Permalänk
Medlem

[C++] Hjälp med Exceptions!

Hej igen!

Har stött på ett problem som mitt trötta huvud inte kan lösa så vänder mig hit! Jag sitter och skriver en kod som innehåller en väldigt lång algoritm för ett litet spel jag håller på och kodar som jag skulle vilja avbryta så fort en viss tid har gått. Min idé var att använda Exceptions (http://www.cplusplus.com/doc/tutorial/exceptions/) för att kunna avbryta algoritmen så fort denna tid har gått, men jag får inte det att fungera.

Nu är min kod ganska lång och väldigt krånglig för utomstående så jag tar och skriver ett kort exempel på det jag vill uppnå:

//Funktion vid namn myalgorithm: int myalgorithm(...) { try{ if ([annan funktion som räknar tiden och returnerar false om en viss tid har gått] == false) { throw 20; } ... // algoritm som kallar sig själv (d.v.s. den kallar myalgorithm(...) )åter och åter igen tills dess att ett villkor uppnåtts return c; // c är en int } //end of try } main { while (...) { int x = myalgorithm(...); catch (int e) { break; //break while-loopen } }

När jag kompilerar min kod (som annars fungerar felfritt utan Exceptions-delen) så får jag följande felmeddelande:

error: expected 'catch' before '}' token

Detta får mig att tro att det inte går att inte går att använda Exceptions på det sättet jag vill (d.v.s. ha try i myalgorithm då den kallar sig själv - catch måste komma direkt efter try enl. länken ovan).

Har ni något förslag på hur jag kan använda Exceptions för att avbryta algoritmen (d.v.s. while-loopen i main)? Jag kan tyvärr inte avbryta den i själva funktionen myalgorithm(...) då den måste returnera int.

Tack!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Hedersmedlem

@Icte
try- och catch-blocken bör nog hänga ihop. Varför inte

while (...) { try { int x = myalgorithm(...); } catch (int e) { break; //break while-loopen }

och utan try-satserna i funktionen? Dock bör man kanske fundera på om det verkligen inte finns en snyggare lösning.

Permalänk
Medlem

@Elgot Funderade på detta också, men hur gör jag då med

if ([annan funktion som räknar tiden och returnerar false om en viss tid har gått] == false) { throw 20; }

?

Går det att behålla throw 20; i myalgorithm(...) eller måste den modifieras på något sätt (har för mig att t.ex. Java använder throws som används i deklarationen av metoden men känner inte till hur man gör detta i C++)?

Ang. snygghet så spelar det nog inte så stor roll då det är endast jag som kommer att läsa koden

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Medlem

Ett sätt att lösa detta utan exceptions skulle vara att returnera ett värde från myalgorithm via en referens, t.ex.:

int myalgorithm(bool &success, ...) { if ([annan funktion som räknar tiden och returnerar false om en viss tid har gått] == false) { success = false; return 0; } ... success = true; }

Permalänk
Hedersmedlem

@Icte
throw kan vara kvar; bara try och måsvingar skall bort.

Permalänk
Medlem

@perost Intressant idé, men måste jag returnera 0 om jag tillämpar det på detta sätt? Själva myalgorithm(...) returnerar väldigt olika tal, ibland 0, ibland 99999 beroende på vad andra algoritmer returnerar vilket kan "bugga" spelet, så hade helst undvikit detta.

Kanske en lite dum fråga, men kan en metod som returnerar int (d.v.s. int myalgorithm(...)) även returnera en bool?

@Elgot Tack, ska testa detta och återkommer!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Medlem
Skrivet av Icte:

@perost Intressant idé, men måste jag returnera 0 om jag tillämpar det på detta sätt? Själva myalgorithm(...) returnerar väldigt olika tal, ibland 0, ibland 99999 beroende på vad andra algoritmer returnerar vilket kan "bugga" spelet, så hade helst undvikit detta.

Kanske en lite dum fråga, men kan en metod som returnerar int (d.v.s. int myalgorithm(...)) även returnera en bool?

Nej, du kan returnera vad du vill, tanken var att värdet som returneras om tiden går ut inte kommer användas och därför inte spelar någon roll.

Men om du alltid returnerar ett positivt tal så kan du göra det enklare för dig, och returnera t.ex. -1 när tiden går ut. D.v.s. returnera ett värde som funktionen normalt inte kan returnera. Då behöver du inte referensen, anroparen kan ta reda på om funktionen lyckades eller inte genom att kolla vad returvärdet är.

Det går också att returnera flera värden från en C++-funktion, t.ex. genom att returnera en struct eller en pair eller tuple.

Permalänk
Medlem

@Elgot Med ditt förslag så kompilerar koden utan problem, men när jag kör den och den når fram till myalgorithm(...) i while-loopen så får jag följande meddelande:

terminate called after throwing an instance of 'int' Aborted (core dumped)

Därefter avslutas programmet, fastän den inte ska göra det (utan endast break:a while-loopen). Är det något annat man bör tänka på att lägga till i myalgorithm(...) för att inte detta ska ske? Eller är det helt enkelt ett konstigt fel som beror på min krångliga algoritm?

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Skrivet av perost:

Nej, du kan returnera vad du vill, tanken var att värdet som returneras om tiden går ut inte kommer användas och därför inte spelar någon roll.

Men om du alltid returnerar ett positivt tal så kan du göra det enklare för dig, och returnera t.ex. -1 när tiden går ut. D.v.s. returnera ett värde som funktionen normalt inte kan returnera. Då behöver du inte referensen, anroparen kan ta reda på om funktionen lyckades eller inte genom att kolla vad returvärdet är.

Det går också att returnera flera värden från en C++-funktion, t.ex. genom att returnera en struct eller en pair eller tuple.

Skulle också försöka lösa detta utan exceptions.
Exceptions bör hantera exceptionella tillstånd (felhantering) i programmet och inte normalt flöde, i min mening.
Att låta ett returvärde indikera att tid gått ut, eller en retur-parameter som föreslås är bättre.
Det är såklart en fråga om tycker och smak...

Visa signatur

"Logic will get you from A to B. Imagination will take you everywhere." - Einstein

Permalänk
Hedersmedlem
Skrivet av Icte:

@Elgot Med ditt förslag så kompilerar koden utan problem, men när jag kör den och den når fram till myalgorithm(...) i while-loopen så får jag följande meddelande:

terminate called after throwing an instance of 'int' Aborted (core dumped)

Därefter avslutas programmet, fastän den inte ska göra det (utan endast break:a while-loopen). Är det något annat man bör tänka på att lägga till i myalgorithm(...) för att inte detta ska ske? Eller är det helt enkelt ett konstigt fel som beror på min krångliga algoritm?

Har du ungefär såhär nu alltså?

//Funktion vid namn myalgorithm: int myalgorithm(...) { if ([annan funktion som räknar tiden och returnerar false om en viss tid har gått] == false) { throw 20; } ... // algoritm som kallar sig själv (d.v.s. den kallar myalgorithm(...) )åter och åter igen tills dess att ett villkor uppnåtts return c; // c är en int } main { while (...) { try { int x = myalgorithm(...); } catch (int e) { break; //break while-loopen } }

Det där felet tyder på att throw körs utanför ett try-block eller att det inte finns någon catch som är lämplig för att fånga argumentet (20 i ditt fall).

Permalänk

hmm skulle du inte kunna retunera ett std::pair<int, bool> istället för int ?

using MyAlgorithmReturnValue = std::mapr<int, bool>; MyAlgorithmReturnValue myalgorithm(...) { if ([annan funktion som räknar tiden och returnerar false om en viss tid har gått] == false) { return std::make_pair(0, false); } // c är värdet som skall returneras. return std::make_pair(c, true) } main { while (...) { MyAlgorithmReturnValue x = myalgorithm(...); if(x.first) { break; } } }

Permalänk
Medlem

@perost @MasterLussifer Hade nog gjort det på det sättet, men tyvärr så returnerar myalgorithm(...) alla heltal mellan -std::numeric_limits<int>::max(); och std::numeric_limits<int>::max(); Men jag ska testa @perost ' förslag också, jag tror det blir enklare utan Exceptions!

@Elgot Ja precis, har exakt så där! Hittade felet - jag hade myalgorithm(...) även i en annan metod utan ett try block. Tack för hjälpen!

@Superhepper Det är också ett alternativ! Ska ta en titt på detta och se om det går att implementera!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk
Skrivet av MasterLussifer:

Skulle också försöka lösa detta utan exceptions.
Exceptions bör hantera exceptionella tillstånd (felhantering) i programmet och inte normalt flöde, i min mening.
Att låta ett returvärde indikera att tid gått ut, eller en retur-parameter som föreslås är bättre.
Det är såklart en fråga om tycker och smak...

Nu tycker jag att du är onödigt restriktiv i användandet av exceptions. I det här fallet är det, i mina ögon, helt OK att använda sig av exceptions. Att tiden gått ut är något som avviker från det normala beteendet och det är just det exceptions är till för. Ett undantag, helt enkelt. Man slipper allt strul det innebär att returnera ett par och att testa resultatet efter varje anrop, på alla ställen i call-kedjan, för att se om det konstiga har skett. Med exceptions kan man sköta all hantering av det konstiga på ett ställe. Detta är ett smash-upplägg för exceptions. Personligen skulle jag definiera en TimeOut-klass och kasta ett sådant objekt snarare än en int för att förtydliga vad det är som har hänt, snarare än att det hänt något konstigt.

Att man rekommenderar att exceptions inte skall användas för "normalt flöde" handlar om att det ofta är ganska dyrt att kasta ett exception (har sett siffror på upp till 1000 gånger dyrare än en vanlig return), men det innebär inte att man bara skall använda exceptions för felhantering. Man skall dock vara medveten om kostnaden, men just i time-out-fallet lär extrakostnaden inte spela någon som helst roll. Å andra sidan, om funktionen anropas ofta och man ofta kommer kasta ett exception, då kanske man skall fundera på om det finns andra sätt att sköta det på, om exekveringstiden spelar roll.

Permalänk
Medlem

@Ingetledigtnamn Kostnaden tänkte jag inte på, men du verkar ha rätt om detta - Exekveringstiden verkar bli längre nu när jag kör med Exceptions. Jag har funderat på lite andra idéer på hur jag ska lösa detta och tänkte göra det på följande sätt:

För närvarande har jag följande kod som har koll på tiden:

bool checkTime(...) { if (viss tid har gått) { return false; } return true; } //Funktion vid namn myalgorithm: int myalgorithm(...) { if (checkTime(...) == false) { throw 20; } ... // algoritm som kallar sig själv (d.v.s. den kallar myalgorithm(...) )åter och åter igen tills dess att ett villkor uppnåtts return c; // c är en int } main { while (...) { try { int x = myalgorithm(...); } catch (int e) { break; //break while-loopen } ... // Innehåller 4 try & catch funktioner till med int x = myalgorithm(...); }

När jag kompilerar och kör koden så blir den märkvärdigt långsammare än när jag körde utan Exceptions. Om jag vill snabba upp den, hur skulle jag göra då i sådana fall? Skulle en omdefinition av bool checkTime(...) på följande sätt se till att extrakostnaden för användning av Exception inte påverkar exekveringstiden alls, eller kommer den alltid att påverka på något sätt (myalgorithm(...) måste exekveras så många gånger som möjligt, det är därför exekveringstiden är viktig)?

void checkTime(...) { if (viss tid har gått) { throw 20; }

EDIT: Nu har jag fastnat.. Hur gör jag då med myalgorithm(...) om checkTime(...) har throw 20;, om jag vill kunna avbryta den mitt i en iteration?
EDIT 2: Löste detta genom att kalla checkTime(...) i början av myalgorithm(...) ! Återkommer med resultat!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk

@Icte: Nu tror jag vi behöver lite mer information. Hur vanligt är det att myalgorithm returnerar "som vanligt" och hur ofta kastar den ett exception? Det kostar bara extra när du kastar ett exception och gör du inte det hundratals gånger per sekund borde du inte ens kunna mäta skillnaden. Jag tror inte det är exceptions som tar tid om det inte är ditt "normala flöde" som använder exceptions. Vill du veta var du spenderar tid får du profilera koden.

Permalänk
Skrivet av Ingetledigtnamn:

Nu tycker jag att du är onödigt restriktiv i användandet av exceptions. I det här fallet är det, i mina ögon, helt OK att använda sig av exceptions. Att tiden gått ut är något som avviker från det normala beteendet och det är just det exceptions är till för. Ett undantag, helt enkelt. Man slipper allt strul det innebär att returnera ett par och att testa resultatet efter varje anrop, på alla ställen i call-kedjan, för att se om det konstiga har skett. Med exceptions kan man sköta all hantering av det konstiga på ett ställe. Detta är ett smash-upplägg för exceptions. Personligen skulle jag definiera en TimeOut-klass och kasta ett sådant objekt snarare än en int för att förtydliga vad det är som har hänt, snarare än att det hänt något konstigt.

Att man rekommenderar att exceptions inte skall användas för "normalt flöde" handlar om att det ofta är ganska dyrt att kasta ett exception (har sett siffror på upp till 1000 gånger dyrare än en vanlig return), men det innebär inte att man bara skall använda exceptions för felhantering. Man skall dock vara medveten om kostnaden, men just i time-out-fallet lär extrakostnaden inte spela någon som helst roll. Å andra sidan, om funktionen anropas ofta och man ofta kommer kasta ett exception, då kanske man skall fundera på om det finns andra sätt att sköta det på, om exekveringstiden spelar roll.

Var nog otydlig. Menade inte att det bara är för felhantering. Men, om det är normalt förväntat beteende att tiden gått ut och att man kan observera detta beteende i ett normalt körande program skulle jag ändå hävda att en lösning utan exceptions är att föredra. Annars får man olika kontroll-flöden, ett exeception-baserat och ett icke-exception-baserat, för två fall som vi normalt kan förvänta oss. Tycker detta är skillnad mot att t.ex. försöka öppna en fil som inte finns. Man kan nog förvänta sig att det då och då händer i ett programmet men är att betrakta som ett undantagstillstånd snarare än normalflöde.

Är det dock så att händelsen av att tiden går ut bara undantagsmässigt kan inträffa, i relativt sällsynta fall, skulle jag också anse att en exception-baserad lösning är helt ok.

Visa signatur

"Logic will get you from A to B. Imagination will take you everywhere." - Einstein

Permalänk
Skrivet av MasterLussifer:

Var nog otydlig. Menade inte att det bara är för felhantering. Men, om det är normalt förväntat beteende att tiden gått ut och att man kan observera detta beteende i ett normalt körande program skulle jag ändå hävda att en lösning utan exceptions är att föredra. Annars får man olika kontroll-flöden, ett exeception-baserat och ett icke-exception-baserat, för två fall som vi normalt kan förvänta oss. Tycker detta är skillnad mot att t.ex. försöka öppna en fil som inte finns. Man kan nog förvänta sig att det då och då händer i ett programmet men är att betrakta som ett undantagstillstånd snarare än normalflöde.

Finessen med exceptions är just att du får två olika kontrollflöden och att de kan hanteras separat. Även om det är förväntat beteende kan det mycket väl vara motiverat att använda exceptions just för att det blir mycket enklare kod än om du efter varje funktionsanrop skall testa på om det var undantagsfallet och särbehandla det. När du ute i verkligheten har 38 funktionsanrop i legacy-kod mellan stället där du upptäcker undantaget och stället där du vill hantera det, då är man beredd att betala de extra cyklerna ett exception kostar, oavsett om det är ett förväntat flöde eller inte.

Permalänk
Skrivet av Ingetledigtnamn:

Finessen med exceptions är just att du får två olika kontrollflöden och att de kan hanteras separat. Även om det är förväntat beteende kan det mycket väl vara motiverat att använda exceptions just för att det blir mycket enklare kod än om du efter varje funktionsanrop skall testa på om det var undantagsfallet och särbehandla det. När du ute i verkligheten har 38 funktionsanrop i legacy-kod mellan stället där du upptäcker undantaget och stället där du vill hantera det, då är man beredd att betala de extra cyklerna ett exception kostar, oavsett om det är ett förväntat flöde eller inte.

Säger inte emot. Det finns definitivt tillfällen då det passar. I det enkla fallet TS beskriver tycker jag inte det. Ytterligare komplicering kan man också få om man sedan blandar dessa exceptions för normal-flöde med exceptions för felhantering. Då kan koden bli riktigt grisig. Och jag är ute i verkligheten. Jobbat professionellt med C++ programmering i snart 20 år. Ha en fin dag med C++-kodandet!

Visa signatur

"Logic will get you from A to B. Imagination will take you everywhere." - Einstein

Permalänk
Medlem

@Ingetledigtnamn .cpp koden som myalgorithm(...) finns i körs flera gånger (ca 100-10000 ggr, beroende på längden på spelet). Jag har flera andra filer som är beroende av just denna .cpp fil, och vid varje körning av denna .cpp fil så returnerar myalgorithm(...) mellan 1-20 tal, beroende på hur lång tid jag tillåter den att köra (allt mellan 0.2 - 1 sekund) innan den returnerar en Exception. Bör även skriva att varje gång myalgorithm(...) anropas i main så ökar dess körtid exponentiellt. Så generellt kan man nog säga att jag kan behöva kasta en Exception per sekund, d.v.s. allt mellan 100-10000 Exceptions totalt under körtiden (kan bli längre vid ett senare tillfälle, men har inte kommit så långt än att jag kan svara på det)!

Visa signatur

12c/24t 4.0GHz (Zen2) • 2x16GiB 3200MHz C14 • RTX 2080 FE 1965MHz 7000MHz • X570 I PW • Ghost S1 MKII

Permalänk

@Icte: Om du inte kastar oftare än en gång per sekund borde det inte märkas. Om du har en klocka med till räckligt hög upplösning kan du pröva att mäta tiden genom att sätta en global variabel till klockan innan du kastar och ta tiden på nytt i catch och se om du ser någon skillnad på den tiden och tiderna för en vanlig funktionsretur.