Liigu peamise sisu juurde

4 postitust sildistatud "C++"

Vaata Kõiki Silte

CMake tutvustus Visual Studio abil (lihtsa konsoolirakenduse tegemine)

· Ühe min lugemine
Infokiir OÜ

Esialgu võib üsnagi aega võtta, et aru saada CMake ülesehitusest ja loogikast. Sai sellepärast tehtud väike demo, kuidas alustada Visual Studio CMake projekti. Teeme lihtsa konsooliprogrammi, mis töötab nii Windows-s kui Linux-s. Samuti näeme, kuidas teha teeke ja neid kasutada.

Demo: https://www.youtube.com/watch?v=2_Rd4uqu4-g

Lähtekood: https://github.com/asjadenet/CMakeProjectDemo

Linke:

https://cmake.org/examples/

http://preshing.com/20170522/learn-cmakes-scripting-language-in-15-minutes/

Nipid linkimiseks, ehk kuidas üle saada 'error LNK2019' vigadest

· 3 min lugemine
Infokiir OÜ

Pidin hiljuti maadlema error LNK2019: unresolved external symbol tüüpi vigadega. Põhiliselt tekivad need siis, kui lingitavat lihtsalt ei leita .lib või .obj failidest. Rohkem lugemist selle kohta on siin: https://msdn.microsoft.com/en-us/library/799kze2z.aspx.

Puuduvad .lib või .obj faild saab Visual Studio-s lisada nii:

Project Properties > Configuration Properties > Linker > Input > Additional Dependencies > add

Kuidas aga õiged .lib ja .obj failid üles leida, kui need on eri kohtades laiali? Tegelikult saab selle üsna ruttu tehtud, kui kasutada õiget tööriista.

Alustada võib kiirest ülevaatest. Seda saab ruttu teada käsklusega:

dir *.lib /b /s

või

dir *.obj /b /s

/b vorming näitab failid koos teekonnaga ilma üleliigse infota.

/s otsib kõigist alamkataloogidest rekursiivselt.

Esiteks tasub meeles pidada seda, et välja võib olla jäänud mõni süsteemi teegi fail.

Näiteks sain veateate:

error LNK2001: unresolved external symbol __imp_timeGetTime

Tihtipeale on kõige kiirem lahendus kasutada üldotstarbelist interneti otsingumootorit. Veidi otsides leiame, et tuleks lisada linkimisse teek WinMM.lib. Kui meil pole aimu, kust selle faili võiks leida, siis aitab meid jälle tavaline dir käsklus:

dir WinMM.lib /s

Kuna neid faile võib olla arvutis mitmeid, siis selleks et näha ka faili kuupäeva ja suurust, jätan meelega ära /b võtme.

Kui internetiotsinguga vastust ei leia, tasub kõik oletatavad kataloogid läbi otsida. Mõnevõrra aega nõuab kõigi binaarfailide läbiotsimine, kuid kui meil pole paremat ideed, siis see võibki just meid aidata.

.lib failide läbiotsimiseks tegin käsufaili findlib.bat:

@echo off
echo otsime .lib failidest %1
for /r %%f in (*.lib) do (
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.13.26128\bin\Hostx86\x86\dumpbin.exe" /exports "%%f" | findstr %1
if errorlevel 0 if not errorlevel 1 echo Leitud failist: %%f & echo.
)

Tulemused:

C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86>findlib.bat timeGetTime
otsime .lib failidest timeGetTime
_timeGetTime@0
Leitud failist: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86\mmos.lib

_timeGetTime@0
Leitud failist: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86\OneCore.Lib

_timeGetTime@0
Leitud failist: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86\OneCoreUAP.Lib

_timeGetTime@0
Leitud failist: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86\OneCoreUAP_downlevel.Lib

_timeGetTime@0
Leitud failist: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86\OneCore_downlevel.Lib

_timeGetTime@0
Leitud failist: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10586.0\um\x86\WinMM.Lib

.obj failidest otsimiseks tegin käsufaili:

@echo off
echo otsime .obj failidest %1
for /r %%f in (*.obj) do (
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.13.26128\bin\Hostx86\x86\dumpbin.exe" /SYMBOLS "%%f" | findstr %1
if errorlevel 0 if not errorlevel 1 echo Leitud failist: %%f & echo.
)

Linkimisel on oluline jälgida, et platvorm (x86 või x64) oleks õige.

Samuti on tähtis, et Run-Time Library klapiks kogu rakenduse lõikes.

Selle kohta saab rohkem lugeda siit: https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx

Tegin väikese MSYS2 bash käsufaili, millega saan teada .lib faili kuupäeva, platvormi ning ka Run-Time Library.

$ cat /usr/bin/libinfo.sh
#! /bin/sh
ls -l $1
"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.13.26128\bin\Hostx86\x86\dumpbin.exe" -headers $1 | grep machine | head -n 1
grep -aPo '.{0,20}DEFAULTLIB.{0,12}' $1 | head -n 3

Viiteid: https://msdn.microsoft.com/en-us/library/799kze2z.aspx

https://docs.microsoft.com/en-us/cpp/porting/overview-of-potential-upgrade-issues-visual-cpp

Avatud laiendamisele, suletud muutmisele ehk C++ ja staatiline polümorfism

· 7 min lugemine
Infokiir OÜ

Kui sissejuhatuseks rääkida veidi raamatutest, siis Robert C. Martin-i sulest ilmunud teos "Agile Principles, Patterns, and Practices in C#" on klassika, mis ilmselt tasub küll igal programmeerijal läbi lugeda. Siin tutvustatud niinimetatud SOLID printsiibid on väärt rakendamist, kuigi alati ei õnnestu see tavaliselt 100%-liselt.

Võtame korraks vaatluse alla "Avatud laiendamisele, suletud muutmisele" põhimõtte, millega on tihedalt seotud ka SRP.

Meenus ammune juuni 2008 MSDN-s ilmunud Jeremy Miller kirjutatud artikkel "The Open Closed Principle". Seal on kasutusel muidugi dünaamiline polümorfism. Kuid C++ võimaldab ka staatilist polümorfismi ilma VTABLE-t kasutamata. Eriti meeldib see mikrokontrollerite programmeerijatele, sest see võimaldab tihtipeale mälu kokku hoida ja töötab ka kiiremini.

Alustame lihtsa näitega, kus kasutame tavalist if-else hargnemist. Me ei järgi siin erilisi printsiipe, kasutame lihtsalt niiöelda oma talupojamõistust:

#include <iostream>

enum class OrderType {
Domestic, International
};

struct Order {
int order_id;
int customer_id;
OrderType orderType;
};

struct SelectAndProcess {
static void execute(Order &order) {
if(order.orderType == OrderType::International)
{
std::cout << "Processing InternationalOrder id:" << order.order_id << '\n';
}
else if (order.orderType == OrderType::Domestic) {
std::cout << "Processing DomesticOrder id:" << order.order_id << '\n';
}
}
};

int main() {
Order domestic{1, 1, OrderType::Domestic};
Order international{2, 3, OrderType::International};
Order domestic2{3, 4, OrderType::Domestic};
SelectAndProcess::execute(domestic);
SelectAndProcess::execute(international);
SelectAndProcess::execute(domestic2);
return 0;
}

Lihtsuse mõttes on kõik struktuurid ja klassid samas failis. Need saaks küll paigutada erinevatesse failidesse, kuid me ei keskendu praegu sellele.

Seda koodi võib vaadata aadressilt: https://godbolt.org/g/GgNsJA. Assembleris on siin 74 rida.

Ülaltoodud kood on ilmselt esimene viis, kuidas me programmeerijatena oleme alustanud. Suureks plussiks on siin lihtsus. Koodi ei ole ka palju ja see on praegu lihtsasti arusaadav. Programm väljastab:

Processing DomesticOrder id:1
Processing InternationalOrder id:2
Processing DomesticOrder id:3

Kogenud programmeerija näeb siin siiski probleeme:

  • struct SelectAndProcess on reaalses elus kindlasti palju keerulisem. Mõistlik oleks, et iga if lause taga olev kood oleks omaette funktsioonis või pigem klassis. Võime ette kujutada (kindlasti oleme ise näinud või ka ise kirjutanud), kui pikaks see execute meetod võib minna.
  • Kui soovime näiteks lisada uue Order tüübi Special, siis peaksime lisame veel ühe if lause ning meie execute meetod venib veelgi pikemaks. Kuidas oleks tulevikus sellist koodi hooldada? See ei ole väga tore. Vigu võib kergesti tulla ja see võib olla aeganõudev.
  • See kood ei vasta ei SRP ega ka OCP põhimõttele.

Järgmisena vaatame kuidas lahendada sama ülesanne dünaamilise polümorfismi abil, kasutades abstraktset baasklassi. Sisuliselt peame muutma vaid SelectAndProcess avalikku klassi.

#include <iostream>

enum class OrderType {
Domestic, International
};

struct Order {
int order_id;
int customer_id;
OrderType orderType;
};

struct OrderHandler {
virtual ~OrderHandler() {}

virtual void process_order(Order &order)= 0;

virtual bool can_process(Order &order)= 0;
};

struct DomesticOrderProcessor : OrderHandler {
void process_order(Order &order) override {
std::cout << "Processing DomesticOrder id:" << order.order_id << '\n';
}

bool can_process(Order &order) override {
return (order.orderType == OrderType::Domestic);
}
};

struct InternationalOrderProcessor : OrderHandler {
void process_order(Order &order) override {
std::cout << "Processing InternationalOrder id:" << order.order_id << '\n';
}

bool can_process(Order &order) override {
return (order.orderType == OrderType::International);
}
};


struct SelectAndProcess {
static void execute(Order &order) {
DomesticOrderProcessor domesticOrderProcessor;
InternationalOrderProcessor internationalOrderProcessor;
OrderHandler *arr[] = {&domesticOrderProcessor, &internationalOrderProcessor};
int size = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < size; ++i) {
if (arr[i]->can_process(order)) {
arr[i]->process_order(order);
return;
}
}
}
};

int main() {
Order domestic{1, 1, OrderType::Domestic};
Order international{2, 3, OrderType::International};
Order domestic2{3, 4, OrderType::Domestic};
SelectAndProcess::execute(domestic);
SelectAndProcess::execute(international);
SelectAndProcess::execute(domestic2);
return 0;
}

Käivitame selle ja tulemus on sama:

Processing DomesticOrder id:1
Processing InternationalOrder id:2
Processing DomesticOrder id:3

Assembleris on ridu gcc-ga kõvasti rohkem: https://godbolt.org/g/itBHRg. Siin on näha ka, et genereeriti vastavad vtable read.

Kui vaadata clang abil genereeritud listingut https://godbolt.org/g/jwHW3r, siis on ridu kõvasti vähem ja ka vtable read puuduvad.

Nüüd siis sama asi staatilist polümorfismi kasutades. Ilma template võimalust kasutades näeb kood välja niimoodi:

#include <iostream>

enum class OrderType {
Domestic, International
};

struct Order {
int order_id;
int customer_id;
OrderType orderType;
};

struct DomesticOrderProcessor {
bool process(Order &order)
{
if (order.orderType == OrderType::Domestic) {
std::cout << "Processing DomesticOrder id:" << order.order_id << '\n';
return true;
}
return false;
}
};

struct InternationalOrderProcessor {
bool process(Order &order) {
if (order.orderType == OrderType::International) {
std::cout << "Processing InternationalOrder id:" << order.order_id << '\n';
return true;
}
return false;
}
};

inline int process_order(InternationalOrderProcessor &orderp, Order &order) {
return orderp.process(order);
}

inline int process_order(DomesticOrderProcessor &orderp, Order &order) {
return orderp.process(order);
}

struct SelectAndProcess {
static int execute(Order &order) {
DomesticOrderProcessor domestic_order;
InternationalOrderProcessor international_order;
if (process_order(domestic_order, order)) return 0;
process_order(international_order, order);
return 0;
}
};

int main() {
Order domestic{1, 1, OrderType::Domestic};
Order international{2, 3, OrderType::International};
Order domestic2{3, 4, OrderType::Domestic};
SelectAndProcess::execute(domestic);
SelectAndProcess::execute(international);
SelectAndProcess::execute(domestic2);
return 0;
}

Assembleri listingus saime 78 koodirida: https://godbolt.org/g/j8PBxU

Kasutasime siin ära selle, et vastavalt meetodi parameetritele käivitatakse ka vastav funktsioon. Järgmisena võtame appi C++ template, mis võimaldab meil koodi mitte korrata:

template<typename order_processor>
int process_order(order_processor &orderp, Order &order) {
return orderp.process(order);
}

kogu programm:

#include <iostream>

enum class OrderType {
Domestic, International
};

struct Order {
int order_id;
int customer_id;
OrderType orderType;
};

struct DomesticOrderProcessor {
bool process(Order &order)
{
if (order.orderType == OrderType::Domestic) {
std::cout << "Processing DomesticOrder id:" << order.order_id << '\n';
return true;
}
return false;
}
};

struct InternationalOrderProcessor {
bool process(Order &order) {
if (order.orderType == OrderType::International) {
std::cout << "Processing InternationalOrder id:" << order.order_id << '\n';
return true;
}
return false;
}
};

template<typename order_processor>
int process_order(order_processor &orderp, Order &order) {
return orderp.process(order);
}

struct SelectAndProcess {
static int execute(Order &order) {
DomesticOrderProcessor domestic_order;
InternationalOrderProcessor international_order;
if (process_order(domestic_order, order)) return 0;
process_order(international_order, order);
return 0;
}
};

int main() {
Order domestic{1, 1, OrderType::Domestic};
Order international{2, 3, OrderType::International};
Order domestic2{3, 4, OrderType::Domestic};
SelectAndProcess::execute(domestic);
SelectAndProcess::execute(international);
SelectAndProcess::execute(domestic2);
return 0;
}

Assembleri listing https://godbolt.org/g/Qf3F9F on tegelikult täpselt sama.

Lõpuks veel sama asi CRTP abil:

#include <iostream>

enum class OrderType {
Domestic, International
};

struct Order {
int order_id;
int customer_id;
OrderType orderType;
};

template<class Derived>
struct base {
void process() {
static_cast<Derived *>(this)->process();
};
};

struct DomesticOrderProcessor : base<DomesticOrderProcessor> {
bool process(Order &order) {
if (order.orderType == OrderType::Domestic) {
std::cout << "Processing DomesticOrder id:" << order.order_id << '\n';
return true;
}
return false;
}
};

struct InternationalOrderProcessor : base<InternationalOrderProcessor> {
bool process(Order &order) {
if (order.orderType == OrderType::International) {
std::cout << "Processing InternationalOrder id:" << order.order_id << '\n';
return true;
}
return false;
}
};

template<typename order_processor>
int process_order(order_processor &orderp, Order &order) {
return orderp.process(order);
}

struct SelectAndProcess {
static int execute(Order &order) {
DomesticOrderProcessor domestic_order;
InternationalOrderProcessor international_order;
if (process_order(domestic_order, order)) return 0;
process_order(international_order, order);
return 0;
}
};

int main() {
Order domestic{1, 1, OrderType::Domestic};
Order international{2, 3, OrderType::International};
Order domestic2{3, 4, OrderType::Domestic};
SelectAndProcess::execute(domestic);
SelectAndProcess::execute(international);
SelectAndProcess::execute(domestic2);
return 0;
}

Genereeritud assembleri listing on ikka sama: https://godbolt.org/g/e4s5iF. Koodi tuli juurde, aga olulist võitu sellest ei tulnud.

Mida öelda kokkuvõtteks?

  • On hea teada nii dünaamilise kui staatilise polümorfismi võimalusi. See aitab kirjutada kergemini hallatavad koodi.
  • Tasub vaadata ja võrrelda genereeritud assembleri listinguid. Optimeeritud kompilaator võib meid üllatada. Näiteks clang-i genereeritud dünaamilise polümorfismiga variant ei sisaldanudki vtable ridu.

linke:

https://www.safaribooksonline.com/library/view/agile-principles-patterns/0131857258/

https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

https://en.wikipedia.org/wiki/Open/closed_principle

https://en.wikipedia.org/wiki/Single_responsibility_principle

https://blogs.msdn.microsoft.com/msdnmagazine/2008/07/02/patterns-in-practice-the-open-closed-principle/

http://download.microsoft.com/download/3/a/7/3a7fa450-1f33-41f7-9e6d-3aa95b5a6aea/MSDNMagazine2008_06en-us.chm

https://en.wikipedia.org/wiki/Template_metaprogramming#Static_polymorphism

Real-Time C++ http://www.springer.com/gp/book/9783662478097