- Egy óra, két rendszer
- iOS alkalmazások
- Melyik napszakban merül majd le az iPhone 17 Air?
- Telekom mobilszolgáltatások
- Samsung Galaxy S24 Ultra - ha működik, ne változtass!
- iPhone topik
- Samsung Galaxy Z Fold7 - ezt vártuk, de…
- Macrodroid
- Samsung Galaxy S22 Ultra - na, kinél van toll?
- Honor Magic7 Pro - kifinomult, költséges képalkotás
Hirdetés
Talpon vagyunk, köszönjük a sok biztatást! Ha segíteni szeretnél, boldogan ajánljuk Előfizetéseinket!
Új hozzászólás Aktív témák
-
dobragab
addikt
válasz
dabadab #4197 üzenetére
Vagy meg inkabb, ha C++11:
struct Point
{
int x = 0;
int y = 0;
Point() = default;
Point(int x, int y)
x(x),
y(y)
{}
};Csak mivel a kollega nem hasznalt inicializalo listat, ezert nem akartam abba bonyolodni, hogy megmutassam. Es beepitett tipusokra konkretan egyenerteku a ketto. Majd ha nincs default ctor vagy van dinamikus memoriakezeles, akkor kezd ertelme lenni megmutatni.
Amúgy meg van olyan kezdőknek szóló könyv, amit úgy meg bírtak írni, hogy a példák vállalhatóak?...
Nekem van egy kezdemenyezesem, de eleg hatarozottan a BME mernokinfos prog2-re van szabva, es a C ismeretere epit.
(#4198) mgoogyi
Amíg egy egyszerű téglalap reprezentációja ennyi magyarázatra szorul, az nekem azt mutatja, hogy nagyon keveset kódolt még.
Igen, es nem latom ertelmet annak, hogy OOP-t tanuljon ennyire koran, de hat...
-
dobragab
addikt
válasz
cadtamas #4190 üzenetére
Most, hogy mar erted is, hadd szidjam kicsit a feladatot.
A legnagyobb problema a Rectangle-ben, hogy redundans adattagok vannak benne. Alapvetoen ez egy olyan teglalapot tarol, aminek az oldalai parhuzamosak a tengelyekkel, tehat kb. ennyi adattagra lenne szukseg:
Point topLeft;
int width;
int height;vagy
Point topLeft;
Point bottomRight;Hiszen ebbol a kettobol ki lehet szamolni az osszes tobbi adatot, egyetlen osszeadassal / kivonassal. Amugy a getter ertelme pont ez, hogy ha valami nem kozvetlenul all rendelkezesre, akkor is ki tudja szamolni / ossze tudja rakni. Ha ennel tobb adatod van, akkor sziszifuszi harc ezt konzisztensen tartani (ertsd: ne legyenek benne egymasnak ellentmondo adatok), ezert 3-5 soros egy setter. Igy kene mondjuk:
Point getLowerLeft() const
{
return Point(topLeft.getX(), bottomRight.getY());
}A masodik baj, hogy tobb setter, pl. a setTopLeft fuggveny szemantikailag ertelmetlen. Egy teglalap bal felso sarkat hogy allitod? Tolod az egesz teglalapot, hogy a merete maradjon meg, vagy megnyujtod, es 2 pontot mozgatsz vele, hogy a jobb also pont maradjon a helyen? Mivel nem egyertelmu, hogy mit csinal, nem szabadna ilyen nevu fuggvenynek lennie. At lehetne nevezni mondjulk pushTopLeft-re vagy dragTopLeft-re, ugy mar egyertelmu, hogy a teglalapot nyujtja, mint PowerPointban a kep atmeretezese.
Altalaban veve pedig a setter rossz omen, azt sugja (haladobbaknak: code smell, tehat nem feltetlenul rossz, csak nagyon nagy valoszinuseggel), hogy valami nem oke a koddal. (haladobbaknak: Single Responsibility Principle-t sert, konkretan ugy, hogy nincs is responsibility.) Itt pl. az, hogy semmit nem csinal az osztalyod, semmivel sem jobb, mint
struct Point { int x; int y; };
A harmadik problema, hogy a Pont-nak kene konstruktor setter helyett.
struct Point
{
int x;
int y;
Point()
{
x = 0;
y = 0;
}
Point(int x, int y)
{
this->x = x;
this->y = y;
}
};Es igy nem kell bele setter sem, mert felul tudod irni az = operatorral, pl.
topLeft = Point(topLeft.x, topLeft.y + 10);
Destruktort megmutatni semmi ertelme, amig nincs eroforraskezeles (tipikusan: dinamikusan foglalt memoria), es a Rectangle osztalyban amugy sem szabad ilyennek lennie (egy teglalap ne kezeljen mar dinamikus adatot, haladobbaknak: ez megint SRP-t sert). De ezt irtak elottem is.
Negyedik: hibas a Point ket settere (ami amugy nem is kene, lasd fent). Igy lenne helyes:
void setX(int x) { itsX = x; }
void setY(int y) { itsY = y; }Igy hirtelen ennyi, ami bokte a csorom. Azert irtam le, mert az OOP-nek (objektumorientalt programozas) jo lenne, ha nem csak a szintaktikai elemeit tanitanak meg, hanem az elveket, ami menten epiteni es alkalmazni illik. Anelkul semmi ertelme az egesznek.
OFF: amugy sajnos nem talaltam olyan konyvet, ahol le vannak ezek irva rendesen, es nem 500+ oldalban. Ha valaki tud ilyet, ne tartsa magaban.
-
dobragab
addikt
válasz
Bruce1967 #4185 üzenetére
VS2015 / VS2017 + ReSharper C++. Amúgy amíg a tanulás legelején vagy, addig CodeBlocks-t érdemes, utána CLion-re érdemes áttérni. VS+ReSharper legnagyobb előnye a CLion-höz képest, hogy nagy kódbázison gyorsabb. pl. a "go to definition" meg ilyen dolgok, azt meg úgyse használod ki.
-
dobragab
addikt
Az, hogy a masodik kodreszlet nem szabvanyos, csak C-ben, C99-tol, C++-ban sose volt az. Egyebkent a neve Variable Length Array.
Az elsovel pedig az a baj, hogy a
delete[]
-et konnyu elfelejteni (vagy nem figyelni valami aprosagra, ami miatt nem fut le, hiaba irtad ki, pl.return
vagy kivetel), sulyos hibaforras.Hasznalj helyette
std::vector
-t, esetleg teddstd::unique_ptr
-be, ha a vector tul "nagy" (arduino-n nem tudom mik vannak). -
dobragab
addikt
válasz
Ereshkigal #4110 üzenetére
Én is erre próbáltam utalni, csak finomabban
-
dobragab
addikt
válasz
EQMontoya #4103 üzenetére
Egyébként ezt is meg lehet csinálni, azért élőben is szoktad látni a próbálkozásaimat
Annyi a lényeg, hogy a szüzet a másik irányból kell támadni. (Mármint a legelvadultabb C++ kiskapukat kell keresgélni és abuse-olni.) Ami szar meló, sok munka van vele, és sose lesz az igazi
-
dobragab
addikt
válasz
jattila48 #4081 üzenetére
Ha capture list van a lambdadban, akkor nem tudod belole siman kikerni a fuggvenypointert. Szerencsere van ra trukk
Ez a standard
signal
-t hivja meg, ami valoban egy pattintott fuggvenypointert var.std::function<void(int)> sh = [capture](int sig) {
std::cout << "Signal " << sig << " caught, captured " << capture << std::endl;
};
signal(SIGINT, tmp_lib::to_fptr(sh, []{}));(#4086) PandaMonium
target()
sem jo, ha objektumot capture-oltel, akkor a target nem lesz soha fuggvenypointer. -
dobragab
addikt
válasz
m.zmrzlina #4008 üzenetére
En Linuxon CLion-t hasznalok, Windowson pedig Visual Studio-t clang toolsettel, es ReSharperrel. Mint IDE, a VS + ReSharper szerintem hangyanyival jobb, plusz gyorsabb es kevesebb ramot zabal. A VS-nel a clang-et is picit mokolni kell, hogy jol mukodjon.
A Visual Studio ReSharper nelkul egy nagyobb projekten mar kinszenvedes, gyotrelmesen lassu az IntelliSense, es nagyon keves funkcioja van az elozo kettohoz kepest.
Ha egyetemista vagy, Jetbrains termekeket (CLion, ReSharper) ingyenesen megkaphatod, ha megfelelo email cimmel regisztralsz. Nalunk a BME-VIK-en pl. automatikusan mukodik.
-
dobragab
addikt
válasz
PandaMonium #3956 üzenetére
std::string
-et megstd::vector
-t lehet ugy hasznalni, hogy nincs kimondva az objektum vagy RAII szo.Nem muszaj tudnia, hogy mi az az egysegbezaras, adatrejtes, peldanyositas, polimorfizmous, adatabsztrakcio, interfesz, amig azokat tanulja, hogy linearis kereses, fuggveny, visszateresi ertek, szamlalas, rendezes. Persze elobb-utobb annak is eljon az ideje, de az eggyel magasabb absztrakcios szint, eggyel kesobb fogja ertelmet latni, es amennyire latom, most meg nem tart ott.
-
dobragab
addikt
válasz
PandaMonium #3954 üzenetére
Amíg alapvető algoritmusokat és adatszerkezeteket nem ismer, addig nem fogja tudni megérteni, ezek mire jók. Annak is eljön az ideje, de semmi értelme úgy írogatnia a class és provate szavakat, hogy nem ismeri a jelentését és az értelmét.
-
dobragab
addikt
Miéhéhéhéhéhéhéért nem veszi észre senki, hogy totál felesleges sztringbe olvasni? A fájlból tessék kapásból az inteket.
int n;
if (!(input >> n))
return shiet; // baj van
// blablablastd::transform-ot pedig azért szeretjük, mert karakterszámra kétszer hosszabb, olvashatóságra háromszor rosszabb, mint a vele ekvivalens for ciklus (range based for-ral)
std::istream_iterator dettó, egyetlen egyszer használtam, input iteratoros overload tesztelésére.
Hogy ne csak fikázás legyen, ha van rá igény, megmutatom szebben, csak most mobilról vagyok.
-
dobragab
addikt
válasz
cadtamas #3875 üzenetére
Némi terminológia, hogy eligazodj azon a könyvön túl is:
mutató == pointer
hivatkozás == referenciaHa a függvény feladata egy változó módosítása, akkor miért nem adjuk meg a függvény visszatérési értékének ezt az értéket és írjuk felül az eredeti változót?
Mert nem mindig lehet megtenni, vagy logikailag nincs értelme, vagy 2 értéket kéne visszaadnia, amit nem lehet, stb.
-
dobragab
addikt
válasz
jattila48 #3857 üzenetére
Végül nem is használtam többszörös öröklést emiatt, megoldottam kompzícióval. Így azonban az ősosztály pointert kell static_cast-olnom egy típusmezőtől függően egyik illetve másik leszármazott osztály pointerré, és ezzel elérni a kompozícióval mindkét osztályban létrehozott tagobjektumot, holott ez lehetett volna ős is.
Jol ertem, hogy a dynamic_cast runtime overhead-je helyett felvettel egy tipusmezot?
Mintha annak nem lenne overheadje, vagy negativ hatasa a karbantarthatosagra.
Ha az interface konstruktora trivialis, akkor a leszarmazott konstruktoraval nem kell foglalkozni, meg virtualis oroklesnel sem. Megoldja a fordito.
a kompozícióval mindkét osztályban létrehozott tagobjektumot
Ezzel ujrakrealtad a diamond-problema alapproblemajat, hogy ketszer szerepel a diamond "csucsa" a memoriakepben (nalad is ketto van), amit a virtual inheritance old meg. Szerintem sokkal egyszerubb lenne inkabb a diamondbol kihagyni a virtual kulcsszavakat, azt' jonapot. Igen, az interface ketszer lesz benne, de abban csak 1-1 vptr van. [link]
Szerintem sokkal kifizetodobb helyenkent leirni azt a dupla static_cast-ot (ahol mindegy, hogy A-ra vagy B-re cast-olod kozepen), mint tipusmezot hasznalni.
Egyebkent javaban sem fenekig tejfel az interface. Ott kotelezo az implementalt fuggvenyeket mind kiirni, akkor is, ha absztrakt. Azaz a linkelt peldaban A-ban es B-ben is kb. meg kellene ismetelni f deklaraciojat. Ja, es a javas interfesznek is van tisztesseges overheadje.
-
dobragab
addikt
-
dobragab
addikt
válasz
dabadab #3844 üzenetére
Mondjuk, kicsit általánosabban is meg lehetne fogalmazni, hogy tuti ne lődd tökön magad:
- Inicializáló listán ne hívj tagfüggvényt
- Inicializáló listán ne add ki a this-t vagy a *this-t
- Ha az osztály polimorfikus (van virtuális függvénye), ugyanezek a kitételek érvényesek a törzsre is.Utóbbi esetben a nem virtuális függvény hívása is veszélyes, figyelembe véve a template method pattern-t és ezt.
-
dobragab
addikt
válasz
EQMontoya #3841 üzenetére
Megnéztem, se gcc 7.1.1, se clang 4.0, se clang-tidy nem találja meg. Utóbbiba mondjuk lehetne egy ilyen check-et építeni, majd rá is kérdezek, mert tényleg oltári szopás lehet egy ilyenből.
Valami clang sanitizer tuti megtalálná, de ez elvileg megfogható statikus ellenőrzéssel is.
Talán a legnagyobb probléma itt az, hogy a ctor valami olyat próbál csinálni, ami nem teljesen feladata
Az a vicc, hogy semmi olyan nem történik, mert a count_a melléktermékeként áll elő a prod kódban is a b.
-
dobragab
addikt
Így van.
Amikor a count_a hívódik, a b adattag még nincs inicializálva, így nettó undefined behaviour.
Nálam természetesen a saját allokátortól valamiért (debug mód, vagy OS security, vagy direkt kiszúrásból) kinullázott memóriaterületet kaptam, ami véletlenül az adott implementációban az üres vektor memóriaképével egyezett (count == 0, capacity == 0, data == nullptr)... Ezen frankón lefutott a push_back. Otthon egyébként debug módban crash, release-ben fut.
Az első megoldást választottam, mert a prod kódban ott nem egy sima int van, hanem egy bonyolult, connection állapotot kezelő cucc, rengeteg adattaggal, az egyik konstruktorparaméterére hívtam egy count_a-szerűséget. És még a vektor is csak metaadat, így a tagfüggvényben csak lestem, miért romlik el minden, és a hitem szerint jól inicializált, sehol máshol nem módosított vektorra csak egy nap debugprintf után gondoltam.
Majd beletúrok az allokációba, hogy debug módban írja felül random adattal a memóriaterületet.
Amúgy meg szerintem kéne kérnünk egy SPOILER taget a topikba
-
dobragab
addikt
Napi mitírki. Szó szerint, hétfő délután óta ezt debuggoltam.
#include <iostream>
#include <vector>
class X
{
int a;
std::vector<int> b;
int count_a(int i)
{
b.push_back(i);
return i*i;
}
public:
X(int i)
: a{count_a(i)}
{ }
void f() const
{
std::cout << b.size() << std::endl;
}
};
int main()
{
X x{4};
x.f();
return 0;
} -
dobragab
addikt
Napi Májkroszoft. Alapszituáció: az
strerror
nem thread safe, lehet, hogy valami globális memóriaterületen van a buffer, amit az strerror ír (!), és két threadről hívva minden szétrohad. Már ez is Microsoft-specifikus, mert *IX-eken az strerror semmit nem ír, csak kikeresi a sztringek közül az aktuálisat.Erre a C11 szabvány megoldása:
errno_t strerror_s(char *buf, rsize_t bufsz, errno_t errnum);
size_t strerrorlen_s(errno_t errnum);Tök frankó, lekéred a hosszát előre, allokálsz valamit neki, majd beleíratod.
Ehhez képest a Microsoft (néhány wide verzió mellett) ezt nyújtja:
errno_t strerror_s(char *buffer, size_t numberOfElements, int errnum);
errno_t _strerror_s(char *buffer, size_t numberOfElements, const char *strErrMsg);Jól látható, hogy az
strerrorlen_s
függvényt sikeresen kifelejetették, és használhatatlan az egész. Azstrerror_s
nem tudja visszaadni sehogy a méretet, ha nem fér bele.És akkor a dokumentáció, ami miatt a maradék hajamat is kitépem:
If strErrMsg is NULL, _strerror_s returns a string in buffer containing the system error message for the last library call that produced an error. The error-message string is terminated by the newline character ('\n'). If strErrMsg is not equal to NULL, then _strerror_s returns a string in buffer containing (in order) your string message, a colon, a space, the system error message for the last library call producing an error, and a newline character. Your string message can be, at most, 94 characters long.
- Miért zárja le sorvéggel?
- Miért pont Unixos sorvéggel?
- Hogy sikerült kitalálni, hogy pont 94 karakteres lehet a saját üzenet? Nem annyi lenne a dolga, hogy ráhív egy strlcpy-t? Miért kell felesleges limitációt bevezetni ide is?These functions validate their parameters. If buffer is NULL or if the size parameter is 0, the invalid parameter handler is invoked, as described in Parameter Validation. If execution is allowed to continue, the functions return EINVAL and set errno to EINVAL.
Ha a buffer csak simán kicsi, akkor is EINVAL-lal tér vissza, és ez sehol nincs ledokumentálva. És hogy miért pont EINVAL, az is rejtély, amikor ott van pont erre, szabványosítva az E2BIG és az ENAMETOOLONG.
_strerror_s, _wcserror_s, and __wcserror_s are not part of the ANSI definition but are instead Microsoft extensions to it. Do not use them where portability is desired; for ANSI compatibility, use strerror_s instead.
Mivan? ANSI kompatibilitás? Az strerror_s sose volt ANSI szabvány része, de az ISO C11-é igen. És - ahogy a Macskafogóban elhangzott - a műsort itt tetszették felejteni. Az a függvény, ami ezt a foshányást használhatóvá teszi, nincs implementálva.
template <size_t size>
errno_t strerror_s( char (&buffer)[size], int errnum); // C++ onlyIn C++, using these functions is simplified by template overloads; the overloads can infer buffer length automatically, eliminating the need to specify a size argument. For more information, see Secure Template Overloads.
Miééééhéhéhéhéért? Ha már C++, lehetne mondjuk std::string-gel visszatérő, ami az egész buffer nyavalyát feleslegessé teszi, az APIt pedig használhatóvá. De nem, a C++ template magic-et arra kell használni, hogy a hetvenes évekbeli borzalmasan szar API fényesebb legyen.
Szóval nekem így sikerült használhatóbbá tennem. Tudtok ajánlani valami jobbat esetleg?
std::string const& stdc_error::strerror(int no)
{
static thread_local std::string result(BUFSIZ, '\0');
// Let's hope strerror_s can't fail unless buffer is too small.
// Anyway, if buffer is small, it returns EINVAL (?)
int err;
while ((err = strerror_s(&result[0], result.size(), no)) == EINVAL)
result.resize(result.size() * 2);
if (err != EINVAL) // should not happen
result = "An unknown error occured in strerror_s.";
// Removing trailing '\n'. Assuming that if it contains a newline,
// it won't be in the middle of the string.
// Looking at this wonderful API, this assumption is risky.
auto pos = result.find_last_of('\n');
if (pos != std::string::npos)
result[pos] = '\0';
return result;
} -
dobragab
addikt
válasz
EQMontoya #3823 üzenetére
Nem. A mutable az ellenség megtévesztése miatt került oda, az élet szülte változatban nem volt mutable.
[=]
valamiért (nem tudom, miért, számomra is rejtély) nem x-et, hanem this-t capture-öli érték szerint, de mivel this az eredeti objektumra mutat, az eredeti x-et változtatja. Tehát 3.(#3822) Domonkos
Funkcionális programozás nélkül elég mélyen lennék a ganéban, így egy teljes natív C++ shell motorja ~2000 sor.
-
dobragab
addikt
Napi mitírki, nem magamtól szültem, élet hozta.
#include <iostream>
struct X
{
int x = 0;
void f()
{
[=]() mutable
{
x = 3;
}();
}
};
int main()
{
X x;
x.f();
std::cout << x.x;
}a) nem fordul
b) 0
c) 3 -
dobragab
addikt
válasz
Hunmugli #3779 üzenetére
C++ szabvány nem engedi meg, hogy ne legyen megadva a visszatérési érték. GCC 4.9 ezek szerint sz*rik rá, ahogy sok minden másra is. [link]
Nem definiált függvény csak akkor fordítási (linkelési) hiba, ha meg is hívod. Ezt gyakran kihasználjuk, pl. template metaprogramozásnál.
int func(int);
double func(char);
using T = decltype(func(1)); // T == int(#3777) EQMontoya
Jogos, erre nem gondoltam. Azt hittem, pointerként kéne kiírnia (
%p
lenne printf-nél), mert ugye függvénypointer.Ezek szerint a kiírásra nincs függvénypointer-overload (persze, C++98-ban nem lehetne megcsinálni, utána meg kompatibilitási okok miatt nem vezetnék be soha).
Fptr
->void*
konverzió pedig adatszegmens - utasításszegmens okok miatt nem implicit. Pedigvoid*
-os overload van. -
dobragab
addikt
válasz
Hunmugli #3771 üzenetére
Ha megnézed a fordító hibaüzenetét, közelebb kerülsz a megoldáshoz.
int x(int(int(int(int(int(int(int(int))))))));
Ez a sor nem egy változó, hanem egy függvény deklarációja.
int x(int (*)(int (*)(int (*)(int (*)(int (*)(int (*)(int (*)(int))))))))
Tehát egy inttel visszatérő függvény, [ami paraméterként egy inttel visszatérő függénypointert vesz át]...., ami paraméterként egy intet vesz át.
Utána, a kiírásnál a függvény automatikusan a címére konvertálódik (ami egy függvényre mutató pointer), az viszont bool-ra (nem tudom pontosan, miért). Ezért ír ki 1-et.
Függvénydeklarációnak viszont nem adhatsz értéket.
Nem véletlenül szokták mondani a C / C++ deklarációkra, hogy syntax only a compiler could love…
-
-
dobragab
addikt
válasz
dobragab #3688 üzenetére
C++-ról megint tanultam újat. Rosszul tudtam eddig, megtaláltam a szabványban, hogy a
throw()
és anoexcept
kompatibilis.Mindezt annak nyomán találtam meg, hogy a fordító által generált move ctor vajon
noexcept(auto)
-e. Igen, az, sőt a throw deklarációkat is helyesen merge-eli. Bár ez a része tökmindegy -
dobragab
addikt
A C++ programozás másik alaptulajdonsága, hogy az operátorokat úgy overload-old, hogy ne legyen kérdéses, ne kelljen rajta gondolkodni, mit is csinál.
Ha két pontot kivonsz egymásból, akkor matematikailag vektort kapsz. Ennek a vektornak a hosszát nem kellene, hogy az operator- kiszámítsa, az a length feladata. Két pont különbsége ugyanis nem egy float. OOP-ban pedig minden tagfv. egyszerre egy dolgot csinál, nem kettőt (különbség + hossz), operator overloadinggal együtt különösen átverős. Pontosan az ilyen operátorok miatt mondják a jávások, hogy az operator overloading olvashatatlan kódot eredményez.
-
dobragab
addikt
válasz
pvt.peter #3696 üzenetére
Jól értem hogy az a
void*
valami userdata, amit a callback regisztrálásánál megadhatsz, és amikor visszahívódik, akkor a callback megkapja? Mert akkor különböző callback-eket érdemes regisztrálni a különböző típusú user data-hoz.void A_callback(void * data)
{
A& a = *static_cast<A*>(data);
// ...
}
void B_callback(void * data)
{
B& b = *static_cast<B*>(data);
// ...
}
register_callback(A_callback, &a);
register_callback(B_callback, &b);Ha ez a helyzet, akkor még lehet rajta ezen túl is szépíteni, elsősorban annak függvényében, hogy a
// ...
részeknek egymáshoz van-e bármi köze. Ha mégsem, légyszí mutass valami példakódot, akár mockolt osztálynevekkel. -
dobragab
addikt
válasz
EQMontoya #3686 üzenetére
Klasszikus értelemben vett hibát egyet sem. Olyat viszont, ami a költő hozzá nem értéséről tanúskodik, többet is.
- Az osztály elnevezése. Ha kommentben kell jelezni, hogy ez bizony
XMLParsingException
, tessék már úgy is nevezni az osztályt. Bár ahogy nézem, itt az osztálynevek módosítva vannak, nem hiszem, hogy az XML-t kiszedted volna belőle.-
std::string
-et érték szerint átvenni a konstruktorban. Elvileg nem baj, sok esetben érdemes így csinálni C++11 óta, ha ugyanis aBaseException
-nek vanstd::string&&
-es konstruktora, egy füst alatt csinálhatsz string-et másoló és mozgató konstruktort, ha az inicializáló listán ezt írod:BaseException{std::move(error_)}
.Mivel pl. az
std::runtime_error
-nak nincs ilyen konstruktora, és az egész kódból sugárzik, hogy C++98-ban írták, így erős a gyanúm, hogy itt csak egy felesleges másolást látunk. Plusz sose szerencsés, ha exception dobása közben dobódik egy másik a másolásnál, mert pl. elfogyott a memória. Ezt viszont nem lehet teljesen kikerülni.- Kézzel forward-olni a konstruktort, mint az állatok. Ráadásul rosszul, lásd előző pont. C++11 óta illik így írni:
using BaseException::BaseException;
- Copy ctor kézzel, mint az állatok. Ha valami magic-et csinál ott, még elfogadható, de akkor az op=-nél is illene, meg amúgy is minek. Ez egy sima kivétel-osztály, az ilyen szarság csak hibalehetőség.
- Virtuális destruktor a leszármazottban. Feltételezem, csak azért csinálta, hogy a destruktort virtuálisnak deklarálja, annak meg így semmi értelme, azt az ősben kellett. Ha a destruktora nem üres, komoly indok kell az egészhez, de attól még lehet jó.
-
throw()
. Már a C++98 szabványosítás körül kiderült, hogy athrow(...)
deklaráció szar, elavult, nincs értelme, csak ront a helyzeten. 2016-ban még "karban tartják" ezt a kódot, és ilyen van benne. Athrow()
ráadásul inkompatibilis anoexcept
-tel, tehát ha tényleg rögzíteni és vizsgálni kell, hogy egy függvény dobhat-e, ez a kifejezés false lesz.if(noexcept(e::~ParsingException())) // ...
- Kivételdobásról nyilatkozni destruktorban. Egyrészt ha konstruktorban bármi dobódik, az
std::terminate
. Másrészt, hathrow
deklaráció sérül, az isstd::terminate
, tehát a destruktornál pontosan ugyanaz történik, akármit nyilatkozik ott. Még hanoexcept
-et ír, az is ugyanazt jelenti, mintha semmit nem ír oda.- Az egysoros tagfüggvények definícióját kiviszi másik forrásfájlba. Ugyan már, kinek fáj, ha inline? Feltételezem, egyikben történik semmi eget rengető, tehát egyrészt a nagy semmiért egy egész függvényhívás lesz (pl. a destruktornál). Másrészt az olvasót baszogatja a tudat, hogy meg kéne nézni, mi van a cpp fájlban, pedig sejti, hogy valószínűleg semmi különös, amiért érdemes lenne megmozdítani az ujját.
Szóval röviden ennyi. Nálam így nézne ki ez az osztály:
struct XMLParsingException : public BaseException
{
using BaseException::BaseException;
}; -
dobragab
addikt
válasz
bandi0000 #3668 üzenetére
A szám beolvasása után dobj bele egy ilyet:
cin >> ws;
Ilyenkor az történik, hogy a szám beolvasásánál te ezt írod be:
"5\n"
A beolvasás megeszi az 5 karaktert, az utána lévő pufferben benne marad az enter. Majd jön a getline, ami enterig olvas. Azonnal megtalálja a pufferben az entert, tehát visszatér üres sztring beolvasása után, mindezt user interaction nélkül.A
cin >> ws
annyit csinál, hogy cin-ről beolvassa és lenyeli az összes whitespace-t, de a többi, értékes karaktert nem. Azért jobb ez, mint a simacin.ignore()
, mert ez azt is le tudja kezelni, ha a user ezt írta be:"5 \n"
(#3669) PumpkinSeed
A clear nem a puffert üríti, hanem a hibajelző flageket
-
dobragab
addikt
válasz
PumpkinSeed #3663 üzenetére
Törekedni kell az angol elnevezésre is, de ez opcionális.
Igen, de amíg tanul, és kezdő, a kutyát nem zavarja szerintem. Aztán ha már párszáz sornál hosszabb programnak áll neki, akkor "kötelező" az angol változónév.
Illetve a camelCase használata változók, függvények és metódusok közben.
Naaaa, ne jávázzunk pls.
Egyrészt ez is camelCase, másrészt ez is CamelCase. A jávás konvenció a lowerInitialCamelCase. C++-ban ez projetkfüggő, hogyan nevezi el a változókat, de csak egyféleképpen.
Az STL konvenciója egyébként a lower_case_with_underscore...
-
dobragab
addikt
válasz
bandi0000 #3659 üzenetére
Naakkor sorolom.
- A bemenet ellenőrzésével erősen ellenjavallott ennyit tökölni. Ha valami nem stimmel, kiírni, hogy szar a bemenet és rage quit. Konzolos program úgyse lesz sose user-friendly, de így elveszik a lényeg a sok marhaság között.
- Ha a
cin.clear()
és acin.sync()
nélkül nem működik egy algoritmus, akkor szar. Ráadásul nem feltétlenül végtelen ciklus, ha kihagyod, csak bizonyos esetekben. Megint csak az van, hogy ne próbálj user-friendly lenni.-
for (i2=db; i2<db; i2++)
Ez így ebben a formában nem csinál semmit. És nem is tudok rájönni, hogy hány darabot kéne randomgenerálni.-
int i2, db, ossz2=0, jegy[20], jelesdb=0;
Ezért dupla nyers hal jár. Egyrészt egy sor, egy deklaráció! Másrészt minden változót akkor deklarálj, amikor szükséged van rá, sose előbb! Ez C++, és kb. minden C89-től különböző nyelv és Linus-tól különböző programozó megengedi.- Tessék mán
for(int i
-t írni, akkor nem kell számozni a ciklusváltozókat (i2
). Lásd előző pont.- A végén az átlagszámítós - ötösszámolós ciklus a lényege az egésznek, azt kellett volna faszán elmagyaráznia a tanárnak. És nem a C++-specifikus beolvasós trükközgetésekkel terhelni az agyatokat, az égvilágon semmi értelme. Feltételezem, hogy nem magyarázta el a lehető legérthetőbben, mert akkor külön for ciklust kaptak volna, ahogy illik.
Így röviden ennyi, ami a kódot érinti.
Na és akkor a kérdésed.
A cin belső flagekben tárolja, hogy volt-e valami hiba a beolvasás során, például ha te számot akartál beolvasni, de a júzer azt írta be, hogy
hesstegfoskód
, akkor a cin nem dob hibát, hanem megjegyzi, hogy valami büdös volt, és te akkor ellenőrzöd, amikor akarod. Ezt acin.fail()
-lel, meg még két hasonló függvénnyel tudod megtenni, mindegyik máskor jelez. Ha a következő beolvasás sikerül, akkor is megmarad a flag, tehát nem írja felül a hibás állapotot. Vagy csak annyit írsz acin.fail()
helyett, hogycin
.if(cin) // nem kell ide semmi, ilyenkor igaz-hamissá konvertálódik
cout << "Minden rendben volt.";
else
cout << "Szar van a palacsintában.";Ezeket a flageket a
clear()
-rel tudod kipucolni, hogy ha az előző beolvasás elfailelt, akkor is lásd, hogy a következő jó-e. -
dobragab
addikt
válasz
bandi0000 #3647 üzenetére
Másik jó megoldás, hogy generálsz egy 90 elemű tömböt. Aztán ötször:
- generálsz egy n-t 0-89-ig,
-tomb[n]
-t kiválasztod
- majdtomb[n]
-t kiveszed tomb-ből. Ez legegyszerűbbentomb[n]
és az utolsó elem cseréjével oldható meg, és ezután már 0-88-ig generálsz n-tKódban még egyszerűbb is.
int megoldasok[5];
int tomb[90];
for(int i = 0; i < 90; ++i)
tomb[i] = i;
for(int i = 0; i < 5; ++i)
{
int n = rand() % (90-i); // jó, éles kódban ne rand-ot használj
megoldasok[i] = tomb[n];
std::swap(tomb[90-1-i], tomb[n]); // vagy sima segédváltozós csere
}Nem próbáltam ki, lehet benne elírás...
-
dobragab
addikt
válasz
jattila48 #3618 üzenetére
A keresést úgy értettem, hogy a
bool symbol_exists(Symbol)
van szétosztva, és nem a find_symbol. Szarul fogalmaztam.Szóval valami ilyen type switch van neked:
Symbol * s = find_symbol("int");
switch(s.type)
{
case KEYWORD:
Keyword * k = static_cast<Keyword*>(s);
// blablabla
break;
case TYPE:
Type * t = static_cast<Type*>(s);
// blablabla
break;
}Ennél mennyivel rosszabb ez?
std::string symbol = "int";
auto it_keyword = keywords.find(symbol);
if(it_keyword != keywords.end())
{
// use *it_keyword
return;
}
auto it_type = types.find(symbol);
if(it_type != types.end())
{
// use *it_type
return;
}A scope kezelést még mindig nem tudom, hogy kéne működnie.
-
dobragab
addikt
válasz
jattila48 #3599 üzenetére
Futásidejű költsége nem a static_cast-nak van, hanem a type switch-nek. De ha az enum értékei 0-n-ig folytonosak, a fordító O(1) jump table-t tud belőle generálni. Pont pár hete néztem meg.
Szerintem az a legtisztább megoldás, ha külön tárolod. Egyedül a keresést kell az összes tárolóra kiterjeszteni, és a scope keresésben azt hívni. A scope kezelése nem tudom, hogy megy, főleg, hogy nem tudom, milyen nyelvről van szó
A hozzáadás tuti nem macerásabb, hiszen hozzáadáskor tudod, milyen típust adsz hozzá, csak a megfelelő kollekcióba kell belerakni.
Aztán ja, nem hiszem, hogy ezeket
std::vector
-ban kéne tárolni, ha a tárolási sorrend nem életbe vágó, és csak egy lehet mindenből. Errestd::set
vagystd::unordered_set
jobb, szerintem az utóbbi. -
-
dobragab
addikt
Szerintem az egysoros fuggvenybe bezsufolt hibak szamat ezzel sikerult maximalizalnia az elkovetonek.
bool static getValue(const MsgType* pMsg, std::string tag, std::string& value)
{
return pMsg->getPair(tag, value);
}Mindezt hasznalja egyetlen helyen:
boost::bind(&getValue, &msg, _1, _2);
-
dobragab
addikt
válasz
jattila48 #3582 üzenetére
Igen, az
static int E
akart lenni.Ha masra nem, arra mindenkepp jo, hogy a
typename
kulcsszot megtanulod becsulni, sot orulsz neki, hogy csak ennyi helyen kell kirakni, mert legtobbszor ki tudja talalni a fordito.(#3583) kispx
Az elsot tenyleg elcsesztem.
Masodik esetben vigyazz, a
::E
global scope. -
dobragab
addikt
válasz
jattila48 #3579 üzenetére
Akkor a tobbi megoldas, termeszetesen nem csak a definialt tipusban ternek el, hanem elvi kulonbseg van.
2.
int A, B;
A * B; // eldobjuk a visszateresi erteket
typedef int D;
template<typename T>
struct C
{
int E;
};
int X;
C<D>::E * X;3. Talan ez faj legjobban. Szintaktikailag ez is helyes.
int C, D, E, X;
C<D>::E * X; -
dobragab
addikt
Feladat: Deklaráld, vagy ne deklaráld a szükséges típusokat, változókat, hogy az alábbi program szintaktikailag helyes legyen.
Hint: egynél több jó megoldás van.
// deklaraciok
int main()
{
A * B;
C<D>::E * X;
} -
dobragab
addikt
Az az Enforcement priceless.
-
dobragab
addikt
-
dobragab
addikt
I'll just leave this here.
-
dobragab
addikt
Van egy tippem. Ezt írtad:
template<class T, class Comp = std::less<T>>
Az utolsó két karakter a C++98 parsing szabályok alapján (mohó algoritmus) egy jobbra shiftelő operátor. Tegyél közé egy szóközt, így:
template<class T, class Comp = std::less<T> >
Amúgy ez gyakorlatilag egy hiba a C++98 szabványban, C++11 szerint a felső változat is jó.
Másik hiba: az
std::less
nincs benne sem alist
, sem azalgorithm
header-ben,functional
kell hozzá.Szerk: látom, közben megtaláltad.
-
dobragab
addikt
Kaptunk új formázást, a Programkód mostantól így néz ki. Aki a régit akarná használni, Monospace + Konvertálatlan kombóval tudja.
template<typename... ARGS>
void print_all(ARGS&&... args)
{
using swallow = int[];
(void)(int[]){0, ((void)(std::cout << args), 0)...};
}Felismeri a nyelvet magától
-
dobragab
addikt
(#3546) dabadab
Ejnye-bejnye, meg se ismernéd a Mestert?
Ezt a frizurát nehéz lenne eltévesztenem.
Amúgy aki nem ismerne, a kérdésem 1:49:50-nél hangzott el
-
dobragab
addikt
Jöttök holnap?
-
dobragab
addikt
Napi mitírki. Aki tudja a választ, ne nagyon lője le. Igazából nem is mitírki, inkább rejtvény.
#include <iostream>
int main()
{
/* declare x here */ ;
std::cout << ((char*)&x - (char*)x); // prints 0
return 0;
}Deklaráld x-et a megjelölt helyen úgy, hogy az alatta lévő sor tényleg 0-t ír ki. Megkötések:
1. nem törölhetsz kódot, csak a megjelölt kommentet "cserélheted ki" valamire
2. nem írhatsz bele pontosvesszőt (a sor végén van egy, csak az elé írhatsz)+1: oldd meg úgy, hogy x-et nem inicializálod.
-
dobragab
addikt
válasz
EQMontoya #3519 üzenetére
Nem nagyon látom, mi van a set-nél, nem áll össze a fejemben.
A legjobb megoldás valóban az, ha mindent arra használunk, amire való. Kivéve persze a template, mert azt template metaprogramming-ra is.
Igen, de hát az emplace pont erre van, hogy meghívja helyetted a konstruktort, még ha explicit is. Pont az ilyen baklövések miatt nem kéne push_back helyett is emplace-t használni, ahogy megállapítottuk
-
dobragab
addikt
válasz
EQMontoya #3502 üzenetére
Csak elkészült ez a válasz, túl régóta írom.
Az emplace_back veszélyét szerintem az első világítja meg legjobban. Nem te hívod meg a konstruktort, így történhetnek olyan konverziók, amit nem szeretnénk.
A második példában az igazi körmös nem az emplace_back, hanem a RAII megsértéséért jár. Sőt, rosszabb: RAII és raw pointer keverése. Ugye resource acquisition is initialization lenne, csakhogy a foglalás után pakolod bele egy unique_ptr-be, itt épp implicit módon.
Ha azt vesszük, hogy a vector deklarációja zömmel három forrásfájllal és 2000 sorral arrébb van, mint ahol belepakolsz, és lehet, hogy rosszul emlékszel, hogy az most A*-ot, vagy unique_ptr<A>-t tartalmaz. push_back-nél ha rosszul emlékszel, a fordító hörögve kiszáll.
Szóval a második esetért közvetve az emplace_back felelős, közvetlenül az, aki nem emlékszik, hogy ott unique_ptr van. Általános esetben is, az emplace_back-nél nem mindig kell tudnod, hogy temporális vagy létező cuccot akarsz-e belerakni, és ez itt a fő veszélyforrás.
Engem kísértetiesen emlékeztet a kérdés a C-s NULL és a 0 esetére, a hatékonyságot kivéve. Mindenhova írhatsz 0-t, ahova NULL-t, de fordítva nem igaz. Most ne keverjük ide a printf-et, talán az az egy kivétel van. Mégis, az olvashatóság kedvéért pointereknél NULL-t, egészeknél 0-t írunk, pedig írhatnánk mindenhova 0-t. Ugyanígy, az olvashatóság kedvéért létező objektumnál tessék push_back-et írni, hogy biztosak lehessünk benne, ott tényleg létező objektumról van szó.
Ennyi indok bőven elég, hogy push_back helyett ne használjunk emplace_back-et.
Továbbmegyek: van egyáltalán létjogosultsága az emplace_back-nek?Ha egy már létező objektumot akarsz belepakolni, a kettő pont ugyanannyira hatékony, ugyanaz történik. Megnéztem, mi a helyzet abban az esetben is, amire az emplace_back elsődlegesen való: temporális objektumnál. Igazából arra ment ki a játék, hogy ilyenkor jobb-e az emplace_back, mint a push_back kiírt konstruktorhívással. Azért mégis csak jobban látni, hogy ott egy temporális objektumot pakolunk bele a vektorba, ha oda van írva.
Ilyenkor szoktam elővenni a Noisy osztályt, ami mindig kiírja, mikor mi történik vele, és számolja, hány példány van belőle, amúgy semmi másra nem jó. Itt a teljes forráskód, a Noisy túl hosszú ahhoz, hogy gátlástalanul bemásoljam, ide csak a lényeget hoztam.
int main()
{
std::vector<Noisy> vec;
vec.reserve (1000);
Noisy n1(1);
vec.emplace_back(n1);
Noisy n2(2);
vec.push_back(n2);
vec.emplace_back(Noisy(3));
vec.push_back (Noisy(4));
vec.emplace_back(5);
}Az elején azért van ott a ronda reserve, hogy ne legyen átméreteződés, az csak összezavar minket.
Az n1 és az n2 esetében kottára ugyanaz történik, ahogy el is várjuk, copy ctor. Noisy(3) és Noisy(4) is egyforma, move ctor. Az igazán érdekes a 4-es és 5-ös összehasonlítása. Annyit nyertünk az 5-össel a 4-eshez képest, hogy egy move ctorral és egy ki-move-olt objektum destruktorával kevesebb. Ezen spórolni meg igencsak barbár dolog C++11 óta.
Ha kikommenteled a move ctort, akkor a fordító sem generál - mert írtunk destruktort -, így a copy ctor hívódik, tehát a push_back lényegesen lassabb lesz.
És mivel az emplace_back eleve C++11, így jó eséllyel van move ctora a tárolt cuccnak. Akkor van igazán értelme az emplace_back-nek, ha nincs move ctor, ilyen-olyan okból. Nem is lehet neki jó move ctort írni - ilyet nehezen tudok elképzelni, de C++-ban semmit nem zárok ki -, vagy C++98-as, nem módosítható API-ból jön.
Ha nincs move ctor, a hatékonyság számít annyit, hogy szerintem elfogadható az emplace_back. Egyéb esetben én azt mondom, fordítva kéne: emplace_back helyett is push_back-et használni: fontosabb az, hogy lásd kiírva a ctort, mint a move ctor overhead-je.
Akkor viszont egy másik probléma jön elő: ha van move ctor, akkor push_back, ha nincs, emplace_back. Ez kb. ugyanolyan rossz, mint az eredeti probléma, szóval temporális objektumra egységesen kéne a kettő közül választani. Nézzük meg a várható kockázatokat és a nyereséget:
push_back: ha nincs move ctor, lassú, de jól olvasható és egyértelmű
emplace_back: ha nincs move ctor, gyorsabb, néhány karakterrel rövidebb. Várható kockázat: egy elrontott emplace_back miatt végtelen debuggolás...Szóval én azt mondom, temporális objektumnál az API függvényében kéne dönteni, hogy az adott projektben melyik. De létező objektumra egész biztosan push_back.
-
dobragab
addikt
válasz
MasterMark #3515 üzenetére
Szerintem EOF miatt, stringstream és fájl esetén máshogy lehet az EOF kezelés. EZT nézegesd.
Amúgy ez is szóba jöhet, ha mondjuk több beolvasás eredményét kell egyszerre ellenőrizned:
while(is.good()) {
// ...
}De ez csak tipp, pontosan nem néztem utána.
-
dobragab
addikt
válasz
MasterMark #3513 üzenetére
Jáj. Erre való az, hogy az istream tud bool-ra konvertálódni. Illetve C++98-ban nem is bool-ra, de úgy lehet használni, mintha. Ez más téma, és gány.
while (ss >> in){
// ...
} -
dobragab
addikt
válasz
MasterMark #3511 üzenetére
Elvileg ugyanúgy, elvégre mindkettő std::istream vagy std::ostream leszármazott.
-
dobragab
addikt
Prog2-höz a sima rand() bőven elég, amúgy nem tudom, hogy nem jutott eszedbe, Czirkos kb. 3-4. előadáson már használta prog1-en.
(#3505) Jester01A Cporta egy nullptr-től is kiakad.
(#3506) dabadab
Azaz. Ha épp nem debuggolsz, a main elejére még illik egy ilyet:
srand(time(NULL)); -
dobragab
addikt
válasz
ToMmY_hun #3496 üzenetére
Ez C++-ban is egy erős elv, előadáson a kolléga még el is mondta, hogy nem hívunk konstruktorban / destruktorban virtuális függvényt, vagy le fogják törni a kezed. Utána belerakja ezt a ZH-ba, én meg javíthatom. Komolyan, legszívesebben annak is megadtam volna a pontot, aki B::f-et írt.
(#3492) sztanozs
A genyaság nincs még kimaxolva, szerintem sosem lehet kimaxolni. Még facsarhatjuk belőle a sz@rt kicsit.
(#3489) ToMmY_hun
Konstruálás során ugye először az ősosztály példányosodik, és mivel az említett kódban annak a példányosítása során hívunk egy virtuális függvényt, ezért nem futhat le olyan objektumhoz tartozó definíciója, ami a hívás pillanatában nem is létezik.
Ez a C++ megoldása a problémára, úgy definiálták a szabványban, hogy ilyen ne történhessen. Történhet viszont rosszabb.
Minden konstruktor csak saját magáért felelős. Így az ősosztályok konstruktorát hívja (+ virtuális örökléses blablabla), majd beállítja a vptr-t a sajátjára, aztán meghívja a tagváltozók konstruktorát, majd lefut a törzs. Fikarcnyit sem foglalkozik azzal, hogy mi van alatta. Nem tud róla, ki származik le belőle. És mivel a vptr-t is majd ezek után állítja csak be a leszármazott a saját vptr-ére, a konstruktorban a vptr még nem a leszármazottéra mutat, hanem a sajátra.
Egyébként a szabványban nincs benne sehol a vtable / vptr, nincs rögzítve, hogy úgy kell implementálni, de úgy érdemes. Sőt, a szabvány úgy lett kialakítva, ahogy a vtable-ös megoldások működnek.
A szabványban volt még egy sor, amit nem részleteztetek. A tagváltozók konstruktorában is az a fv. fog hívódni, ami a hierarchiában "nincs lejjebb", azaz a saját vagy "feljebb" van.
Ha a konstruktorban hívott f tisztán virtuális, akkor nem fog fordulni, mert a linker nem találja meg a függvényt. Tisztán virtuális függvény lehet definiálva is, ez most mellékszál. Itt nem kell a vtable-höz nyúlni, mert nyilvánvaló, melyik típusnak a függvényét kell hívni. Ha viszont nem maga hívja meg, hanem közvetve egy olyan függvény, ami nem tudja, pontosan milyen típust is kapott, akkor muszáj lesz a vtable-ben megnéznie. Akár tagváltozó konstruktorában is, mint írtam. Tadamm, így lehet tisztán virtuális függvényt meghívni.
#include <iostream>
class A;
struct member
{
A * a;
member(A* a);
};
struct A
{
member m = this;
A() { }
virtual void f() = 0;
virtual ~A() {}
};
member::member(A * a) : a(a)
{
a->f();
}
struct B : public A
{
void f() { std::cout << "B::f\n";}
};
int main()
{
B b;
return 0;
}
Új hozzászólás Aktív témák
● ha kódot szúrsz be, használd a PROGRAMKÓD formázási funkciót!
- Sony MILC fényképezőgépcsalád
- Egy óra, két rendszer
- Gumi és felni topik
- AMD Catalyst™ driverek topikja
- Fortnite - Battle Royale & Save the World (PC, XO, PS4, Switch, Mobil)
- Androidos fejegységek
- Kerti grill és bográcsozó házilag (BBQ, tervek, ötletek, receptek)
- Autós topik
- iOS alkalmazások
- Kerékpárosok, bringások ide!
- További aktív témák...
- HIBÁTLAN iPhone 16 Pro 256GB Black Titanium -1 ÉV GARANCIA - Kártyafüggetlen, MS3066, 100% Akksi
- GYÖNYÖRŰ iPhone 16 Pro 256GB Black Titanium -1 ÉV GARANCIA - Kártyafüggetlen, MS3065, 100% Akksi
- MacSzerez.com - iPhone 12 Pro Max / 128GB / Asztro / Új gyári akku! / Garancia!
- MacSzerez.com - 2017 MacBook Pro 15" / i7 2.9GHz - 4 Mag / 16GB RAM / 512GB SSD / Radeon Pro 4GB
- MacSzerez.com - iPhone 13 Pro Max / 128GB / Asztro szürke / Garancia!
- DELL Latitude 7340 i7-1365U 16GB 512GB 13.3" FHD+ TouchScren 1 év garancia
- Nvidia Quadro M2000/ M4000/ P2000/ P2200/ P4000/ P5000/ RTX 4000/ RTX A2000
- Steam, EA, Ubisoft és GoG játékkulcsok, illetve Game Pass kedvező áron, egyenesen a kiadóktól!
- Motorola G72 128GB Kártyafüggetlen 1 év Garanciával
- LG 34GS95UE - 34" Ívelt OLED / QHD 2K / 240Hz & 0.03ms / 1300 Nits / NVIDIA G-Sync / AMD FreeSync
Állásajánlatok
Cég: FOTC
Város: Budapest