Portál AbcLinuxu, 4. května 2025 23:03
I když se z C++ dají volat C knihovny přímo, přijde mi lepší si nad tím udělat třídy v C++, kterými se to obalí, což usnadní správu paměti a člověk nezapomene příslušný zdroj zavřít – prostě se to zavře samo, když skončí platnost proměnné.
C knihovny většinou vrací při otevření zdroje (např. USB zařízení) nějakou strukturu a na konci je potřeba ji poslat do nějaké close()
funkce, kde se daná knihovna postará o uvolnění zdrojů. V C++ si tuto strukturu vytvoříme v konstruktoru a uložíme do instanční proměnné. V destruktoru pak zavoláme tu close()
funkci. Tohle vypadá jednoduše a v funguje to… dokud instanci naší třídy nepředáme někam dál jako parametr, což způsobí kopii a následně se zavolá close()
v destruktoru kopie, což nám ale znefunkční i původní instanci. Takže je potřeba zakázat kopírování (privátní kopírovací konstruktor) a objekt předávat jen referencí. Došel jsem k něčemu takovému, když jsem si hrál s USB HID API:
class HIDDevice { private: hid_device* handle; HIDDevice(const HIDDevice& other) : handle(other.handle) { } public: HIDDevice(unsigned short vendorId, unsigned short productId, const wchar_t *serialNumber) { int initError = hid_init(); // TODO: move to HIDContext class? if (initError) throw HIDException(L"Unable to init HID API."); handle = hid_open(vendorId, productId, serialNumber); if (handle == nullptr) throw HIDException(L"Unable to open HID device. Are you root? Is mouse present?"); } virtual ~HIDDevice() { hid_close(handle); hid_exit(); // TODO: move to HIDContext class? } const std::wstring getManufacturerName() const { std::array<wchar_t, 200 > buffer; int error = hid_get_manufacturer_string(handle, buffer.data(), buffer.size()); if (error) throw HIDException(L"Unable to get manufacturer name."); return buffer.data(); } const std::wstring getProductName() const { std::array<wchar_t, 200 > buffer; int error = hid_get_product_string(handle, buffer.data(), buffer.size()); if (error) throw HIDException(L"Unable to get product name."); return buffer.data(); } void sendFeatureReport(std::vector<unsigned char> data) { int written = hid_send_feature_report(handle, data.data(), data.size()); if (written < 0) throw HIDException(L"Unable to send feature report."); } };
Je to takhle správně? Jaké jsou další možnosti?
Když si vystačím s předáváním referencí, tak mi to přijde funkční. Ale co když chci ten objekt předat někam dál a nechat ho tam žít, i když původní metoda skončí (a tím zanikne i ta původní proměnná)? Je potřeba to předávat obalené v chytrém ukazateli? To by fungovalo, ale přijde mi to trochu ošklivé, znepřehledňuje to kód. Víc by se mi líbilo, kdyby se předávala kopie a ten chytrý ukazatel si udržovala uvnitř. Znamená to tedy nutnost si udělat další třídu, která se bude spravovat v tom vnitřním chytrém ukazateli a bude držet jen to hid_device
a ve svém destruktoru ho zavírat? Jak byste tohle řešili? V zásadě bych chtěl, aby se HIDDevice
chovalo jako chytrý ukazatel a rád bych to implementoval nějak elegantně s minimem kódu.
/* Header */ class HIDDevice { public: HIDDevice(const HIDDevice &other) = delete; HIDDevice(HIDDevice &&other); HIDDevice(unsigned short vendorId, unsigned short productId, const wchar_t *serialNumber); virtual ~HIDDevice(); private: hid_device *m_handle; /* ...impl... */ }; /* Implementace */ HIDDevice::HIDDevice(HIDDevice &&other) : m_handle{other.m_handle} { other.m_handle = nullptr; } HIDDevice::~HIDDevice() { if (m_handle != nullptr) cleanup(); }Dále bych se vyhnul věcem jako
unsigned short
a použil přímo uint16_t
, kde je velikost proměnné zaručena.
Dík, tohle vypadá trochu líp a přesouvací konstruktor mi tam chyběl.
To unsigned short
jsem tam dal, protože to je typ, se kterým pracuje ta knihovna, to neovlivním. Maximálně bych to mohl přetypovávat a kontrolovat, že na sebe ty typy pasují.
Jinak bych ale potřeboval vyřešit tu otázku, co dělat, když nestačí předávání referencí. Tzn. vlastně jak nějak elegantně udělat z téhle třídy chytrý ukazatel. (můžu si udělat tu další třídu a dát chytrý ukazatel dovnitř HIDDevice
, ale přijde mi to dost kostrbaté)
V C++11 jde udělat třebaTo
unsigned short
jsem tam dal, protože to je typ, se kterým pracuje ta knihovna, to neovlivním. Maximálně bych to mohl přetypovávat a kontrolovat, že na sebe ty typy pasují.
static_assert(sizeof(unsigned short) == sizeof(uint16_t), "Mismatching unsigned short vs. uint16_t sizes"));
A kdy přesně by to nemělo stačit? S move konstruktory a referencemi jde IMHO vyřešit většina případů. V případě, kdy je nutné sdílet nějaký zdroj z více míst lze použítJinak bych ale potřeboval vyřešit tu otázku, co dělat, když nestačí předávání referencí. Tzn. vlastně jak nějak elegantně udělat z téhle třídy chytrý ukazatel. (můžu si udělat tu další třídu a dát chytrý ukazatel dovnitř
HIDDevice
, ale přijde mi to dost kostrbaté)
std::shared_ptr
. Pro objekty, na které je naopak nutné ukazovat právě jednou existuje std::unique_ptr
.
S move konstruktory a referencemi jde IMHO vyřešit většina případů.
Když tu instanci budu vracet jako návratovou hodnotu, tak to bude OK, ale když budu muset někde použít std::move()
tak mi v té proměnné zůstane stará instance v nějakém divném/nepoužitelném stavu, přitom na ní ale půjdou volat metody (akorát to asi za běhu bude padat nebo se chovat divně), ne? To už mi přijde lepší to počítání referencí.
V případě, kdy je nutné sdílet nějaký zdroj z více míst lze použít std::shared_ptr.
Ano, ale obalovat všechno do std::shared_ptr
mi přijde ošklivé – v signaturách metod to působí jako bordel a znepřehledňuje to kód. Elegantnější by mi přišlo, kdyby ten chytrý ukazatel byl schovaný uvnitř toho HIDDevice
.
Ještě mě napadá tohle:
using HIDDevice_p = std::shared_ptr<HIDDevice>; HIDDevice_p x(new HIDDevice(0,0,0)); x->getProductName();
To už vypadá trochu líp, když tam nestraší všude std::shared_ptr
.
BTW: existuje nějaká konvence pro pojmenování takových typů? (jako jsem tady použil to _p
)
ale když budu muset někde použít std::move()
tak mi v té proměnné zůstane stará instance v nějakém divném/nepoužitelném stavu, přitom na ní ale půjdou volat metody (akorát to asi za běhu bude padat nebo se chovat divně), ne?
No, ten vnitřní pointer bude nullptr
. Jinak ano, tohle je bohužel velký problém move semantics C++, které kvůli tomu vlastně ani pořádně move semantics nejsou, spíš je to takový swap s prázdným objektem. Ale nedá se s tím nic moc dělat.
V praxi to můžeš brát tak, že přistupovat k objektu po std:move()
není validní. Je to podobné jako třeba invalidace iterátorů u kontejnerů a podobně. Prostě se vynasnažit po move
k proměnný nepřistupovat.
Nezůstane. Move se používá v případě, že daný objekt v nějakém kontextu už nepotřebuješ ale zároveň je potřeba, aby dál existoval někde jinde. Provádět move na proměnných, ke kterým by mohlo chtít něco později přistupovat ze stejného kontextu je samozřejmě špatně.S move konstruktory a referencemi jde IMHO vyřešit většina případů.Když tu instanci budu vracet jako návratovou hodnotu, tak to bude OK, ale když budu muset někde použít
std::move()
tak mi v té proměnné zůstane stará instance v nějakém divném/nepoužitelném stavu, přitom na ní ale půjdou volat metody (akorát to asi za běhu bude padat nebo se chovat divně), ne?
Refcounting řeší trochu jiný problém. Move ti umožňuje vytvořit právě jednu instanci nějaké třídy a tu různě přesouvat, aniž by se kdy zkopírovala, zničila a vytvořila znovu atp.To už mi přijde lepší to počítání referencí.
Tím si tam ale nacpeš další úroveň indirekce, která je v principu zbytečná.V případě, kdy je nutné sdílet nějaký zdroj z více míst lze použít std::shared_ptr.Ano, ale obalovat všechno do
std::shared_ptr
mi přijde ošklivé – v signaturách metod to působí jako bordel a znepřehledňuje to kód. Elegantnější by mi přišlo, kdyby ten chytrý ukazatel byl schovaný uvnitř tohoHIDDevice
.
Od C++11 dále existujeJeště mě napadá tohle:
using HIDDevice_p = std::shared_ptr<HIDDevice>; HIDDevice_p x(new HIDDevice(0,0,0)); x->getProductName();To už vypadá trochu líp, když tam nestraší všude
std::shared_ptr
.BTW: existuje nějaká konvence pro pojmenování takových typů? (jako jsem tady použil to
_p
)
auto
, které je možné psát místo konkrétního jména typu. Nelze použít úplně všude ale velkou část explicitního vypisování typů ušetří. Vytvořit si alias na nějaké často používaný typy, jak navrhuješ, je samozřejmě dobrý nápad.
To už mi přijde lepší to počítání referencí.K tomu mě napadá, že si můžeš ten
std::shared_ptr
hodit naopak dovnitř té třídy HIDDevice
- obalit tím ten C API handle s tím, že bys ten std::shared_ptr
vytvořil s tou uvolňovací C API funkcí coby custom deleterem. Pak by ta třída HIDDevice
mohla být kopírovací a fungovalo by to korektně. Ale za cenu refcountingu a indirekce. Nedělal bych to, pokud to není nutný.
class HIDDevice {
private:
std::shared_ptr<hid_device> m_handle;
public:
HIDDevice(unsigned short vendorId, unsigned short productId, const wchar_t *serialNumber) {
auto *handle = hid_open(vendorId, productId, serialNumber);
if (handle == nullptr) {
throw HIDException(L"Unable to open HID device. Are you root? Is mouse present?");
}
m_handle.reset(handle, hid_close);
}
// ...
};
Ale jak říkám, nedělat to, pokud sdílenej přístup z více míst není potřeba handle.reset(hid_open(vendorId, productId, serialNumber), hid_close); if (handle == nullptr) throw HIDException(L"Unable to open HID device. Are you root? Is mouse present?");
shared_ptr
má operator bool()
, kterej slouží na tohle.
Nicméně doporučuju psát tak, aby to bylo pokudmožno čitelný, tzn. třeba nedávat moc logiky do jednoho řádku... Tohle je samozřejmě otázka stylu a ne nějaká objektivní pravda... Osobně jsem fanoušek stylu psát závorky { ... }
okolo if, for, while atd. bloků vždy, aby byl kód pokudmožno jednoznačný.
Dík, to je dobrý nápad.
Chápu, že každý z těch přístupů má něco do sebe, budu si pamatovat oba a zkusím používat, co se kde hodí :-) I když zrovna v tom, co teď píšu, je to úplně jedno – je to jen jednoúčelový prográmek k nakonfigurování myši.
Z vedlejší diskuse:
1) Já bych začal tím, že bych rozhodně nikdy nepoužíval typ uint16t. A to jednoduše proto, že takový typ na mnoha platformách vůbec nemusí existovat. Pokud platforma/procesor nedokáže poskytnout 16bitový integer, tak prostě v C/C++ vůbec uint16t neexistuje.
Zrovna 16bitový integer na mnoha 32bitových platformách vůbec neexistuje.
Ty typy moc neovlivním, vychází z té céčkové knihovny… Kdybych to chtěl víc řešit, tak si udělám nějakou abstrakci a víc to zobecním, aby časem šla použít třeba jiná knihovna, a pak bych použil i vlastní typy. Ale tady se to snažím dělat víceméně 1:1, jen objektově.
2) Jinak pro určování životnosti existuje věc, které se říká počítání referencí. Pokud dané třídě dáte statickou proměnnou s počtem kopií odkazu, řeší to problém. Jakmile počet odkazů klesne na nulu, zrušíte i zdroj. Pokud chcete mít multithreading prostředí pro inkrementaci a dekrementaci čítače referencí použijete atomické funkce.
Vytvoření kopie třídy inkrementuje čítač referencí. Destruktor třídy pak dekrementuje čítač referencí, a pokud dosáhl nuly, tak zavře handle.
Což znamená implementovat vlastní chytrý ukazatel. Jako cvičení si to asi zkusím… ale že by se mi to chtělo dělat pokaždé, když chci obalit C knihovnu do C++, to se říct nedá. Je to běžný a doporučený postup?
Ponkrácovsky korektní kód by se dal napsat třeba taktoZ vedlejší diskuse:
1) Já bych začal tím, že bych rozhodně nikdy nepoužíval typ uint16t. A to jednoduše proto, že takový typ na mnoha platformách vůbec nemusí existovat. Pokud platforma/procesor nedokáže poskytnout 16bitový integer, tak prostě v C/C++ vůbec uint16t neexistuje.
Zrovna 16bitový integer na mnoha 32bitových platformách vůbec neexistuje.
Ty typy moc neovlivním, vychází z té céčkové knihovny… Kdybych to chtěl víc řešit, tak si udělám nějakou abstrakci a víc to zobecním, aby časem šla použít třeba jiná knihovna, a pak bych použil i vlastní typy. Ale tady se to snažím dělat víceméně 1:1, jen objektově.
static_assert(sizeof(unsigned short) >= sizeof(uint_least16_t), "Mismatching unsigned short vs. uint_least16_t sizes");a používat
uint_least16_t
.
Rozhodně není. C++11 má plný komplement automatických ukazatelů, které stačí prostě použít.2) Jinak pro určování životnosti existuje věc, které se říká počítání referencí. Pokud dané třídě dáte statickou proměnnou s počtem kopií odkazu, řeší to problém. Jakmile počet odkazů klesne na nulu, zrušíte i zdroj. Pokud chcete mít multithreading prostředí pro inkrementaci a dekrementaci čítače referencí použijete atomické funkce.
Vytvoření kopie třídy inkrementuje čítač referencí. Destruktor třídy pak dekrementuje čítač referencí, a pokud dosáhl nuly, tak zavře handle.
Což znamená implementovat vlastní chytrý ukazatel. Jako cvičení si to asi zkusím… ale že by se mi to chtělo dělat pokaždé, když chci obalit C knihovnu do C++, to se říct nedá. Je to běžný a doporučený postup?
Což znamená implementovat vlastní chytrý ukazatel. Jako cvičení si to asi zkusím…Nic takového prosímtě nedělej, ta Ponkrácova rada je úplne mimo, zbytečně tam zanáší další indirekci, navíc přes holý pointer. Dobře ti radí MadCatX - potřebuješ move konstruktor. Ještě bych analogicky doimplementoval move
operator=()
a potom kopírovací operator=()
označil delete
stejně jako to je s kopírovacím konstruktorem.
unsigned short
úplně klidně používej, pokud to je typ, který používá to C API.
Pokud k tomu budeš potřebovat přistupovat nějak sdíleně, použij nejspíše shared_ptr
, ale nejdříve bych se rozmyslel, jestli to je opravdu potřeba.
V zásadě bych chtěl, aby se HIDDevice chovalo jako chytrý ukazatel a rád bych to implementoval nějak elegantně s minimem kódu.Tak to proste pouzij jako chytry ukazatel (std::shared_ptr) pri predani se proste jenom zvysi counter a kdyz to klesne na 0, tak se zavola destruktor. Nebo primo na tom hid_device, ale tam uz budes potrebovat deleter.
hid_device*
std::shared_ptr
, ci std::unique_ptr
dle toho, zda chci ci nechci povolit kopii, uz tu receno bylo. Nemusis pak psat ani destruktor a move konstruktor, implicitni by se mel vytvorit sam. Musi tedy byt specifikovany deleter tech smart pointeru...
U const std::wstring getManufacturerName() const
je zbytecne ten prvni const psat, ale to by ti mel rict prekladac. A hlavne bych na to pustil valgrind, neb mi prijde, ze v implementaci vracis data toho std::array, kteremu se hned zavola destruktor.
A jinak ano, pokud jsi v C++ s vyjimkama, je RAII v podstate nutnost.
ze v implementaci vracis data toho std::array, kteremu se hned zavola destruktorTo je ok, protože nejprve se vytvoří wstring, ten si spočte velikost řetězce (předpokládá ukončovací nulu), naalokuje si buffer, data zkopíruje a teprve poté zaniká původní std::array.
Takže ty možnosti vlastně jsou:
HIDDevice
a obalit jím jen ten to hid_device
případně si počítat reference ručně. O tomhle jsem věděl, ale nenapadlo mne, že není potřeba dělat další třídu a do toho chytrého ukazatele jde dát rovnou to hid_device
+ nastavit mazací funkci.HIDDevice
a předávat tu. To mi původně přišlo ošklivé, ale je fakt, že když si uděláme alias, tak to už vypadá v pohodě.V programu, který teď píši, bych si vystačil s první možností, ale cílem otázky bylo spíš si ujasnit možnosti a vybrat nějaké široce použitelné řešení – dejme tomu, že bych psal obecné C++ API pro nějakou knihovnu a nevím, jak ho kdo bude chtít používat a jestli si vystačí s předáváním ukazatelů/referencí nebo zda bude chtít sdílet vlastnictví… Z pohledu uživatele mi přijde nejpohodlnější ta druhá možnost.
V programu, který teď píši, bych si vystačil s první možností, ale cílem otázky bylo spíš si ujasnit možnosti a vybrat nějaké široce použitelné řešení – dejme tomu, že bych psal obecné C++ API pro nějakou knihovnu a nevím, jak ho kdo bude chtít používat a jestli si vystačí s předáváním ukazatelů/referencí nebo zda bude chtít sdílet vlastnictví… Z pohledu uživatele mi přijde nejpohodlnější ta druhá možnost.IMO obecně je lepší poskytovat unique ownership objekty, protože když bude uživatel potřebovat, tak si to obalí do
share_ptr
, případně může třeba chtít použít i nějaké vlastní smart pointery (share_ptr
není až taková výhra pro multithreading). Tj. dává mu to širší možnost z hlediska composability. Když poskytneš pouze ref-counted objekt, tak s tím uživatel nemůže nic moc dělat - ten ref-counting z vnitřku neodstraní.
Konkrétně v tomhle případě je trochu pitomý, že to C API vyžaduje pointer, takže když napíšeš tu unique ownership variantu a následně to dáš do shared_ptr
, bude tam zbytečně dvojtá indirekce. Takže leda mít obě varianty.
HIDDevice
nekopírovatelný a obalil ho sdíleným ukazatelem. Pak jsou zde ovšem situace, kdy může být vhodnější poskytovat už rovnou nějaký těžkotonážní wrapper, který bude řešit třeba konkurenci a podobně. Jako vždycky platí, že to nejsprávnější řešení vždy závisí na konkrétní situaci.
Doporučím předat vector const referencí:void sendFeatureReport(std::vector<unsigned char> data)
const std::vector<unsigned char>& data
Ušetříš spoustu alokací, pokud to budeš volat v cyklu a zároveň neplánušej vytvářet nový vektor pro každé volání (a tedy mít možnost použít move ctor vektoru).
const &
jsem přidal, pořád na to někdy zapomínám.
Jestli k tomu máš nějaké konkrétní připomínky, tak sem s nimi, rád to vylepším.
Nic konkrétního nemá; koneckonců je to anonym.
push_back
se s C++11 a výše doporučuje používat emplace_back
. Rozdíl je následující:
#include <vector> class Pig { public: Pig(int weight, bool male) : m_weight{weight}, m_male{male} {} private: int m_weight; bool m_male; }; int main() { std::vector<Pig> swines{}; swines.push_back({120, false}); swines.emplace_back(120, false); swines.push_back(120, false); /* Chyba */ return 0; }Append do vektoru s použitím
emplace_back
může použít "in-place" inicializaci, kdy nedochází ke zbytečnému vytvoření dočasného objektu, který se pak zkopíruje.
Místo
Frequency frequency = Frequency::Hz_1000;se v C++11 doporučuje
auto frequency = Frequency::Hz_1000;Herb Sutter na téma extenzivního používaní
auto
měl na CppConu přednášku, skoro mám dojem, že to byla tahle. Pro jednoduché typy se to zdá jako zbytečnost ale jakmile budeš mít kód jako tohle
std::shared_ptr<std::vector<Pig>> getSwines(); auto swines = getSwines();začne to mít smysl. V C++11 ještě existuje nová sytaxe volání konstrukorů složenými závorkami:
Pig peppa{66, false};V případě, že se volá konstruktor bez argumentů, považuje se za idiomatický zápis
str::string s{}
.
Smysl by to mělo na Windows, které interně používají UTF-16.Bohužel ani to ne. Windows nepodporují pořádně UTF-16, ale spíš jakýsi prasopes mezi UCS-2 a UTF-16, tady na HN to někdo nazval UCS2 + surrogates, což mi přijde výstižný.
wchar_t
a std::wstring
je odvozený z toho Windowsího modelu (a bohužel také stringové typy v Qt, Javě, JavaScriptu, ...) a v podstatě mi nepřijde užitečný vůbec k ničemu. Korektní podporu Unicode nezajistí, principielně to je podobný jako mít UTF-8 data v std::string
u. Asi kdyby člověk psal Windows-specific software používající Windows API, tak by to třeba smysl mělo, že by se nemuselo konvertovat sem a tam, ale v multiplatformním SW vzniklém na Unixu IMO nemá wchar_t
vůbec žádnou výhodu.
A co takový printf()
a zarovnávání textu pod sebe? Ono sice ani wprintf()
nefunguje vždy1, ale většinou ano – zatímco printf()
se std::string
/ char
se rozsype i na naprosto základních znacích (např. čeština s diakritikou) kvůli tomu, že ty znaky jsou vícebajtové.
Kromě toho jsem už tolikrát zažil špatně napsané programy, které implicitně předpokládaly nějaké kódování a mršily data... takže za správný postup považuji explicitní konverze resp. měl bys to mít pod kontrolou a vědět, v jakém kódování je vstup, v jakém výstup… a typicky to vede na konverzi na nějakou interní reprezentaci.2 Ten wchar_t
není ideální, ale v rámci standardní knihovny je to asi jediná jakž takž použitelná možnost. Různé knihovny to dělají různě, Qt má něco svého, Apache Xerces má char16_t
, takže se to pak bohužel konvertovat musí sem tam. Zlatá3 Java, kde je prostě String
a tuhle třídu používají všechny knihovny.
[1] u znaků, které mají na obrazovce šířku dvou znaků, což je IMHO trochu prasárna samo o sobě
[2] případně mít nějakou lehkou abstrakci, která by se obešla bez konverzí v případě, že vše bude v jednom kódování… ale tam je otázka, jestli to jen přeposílat dál jako surové bajty, nebo jestli to nějak validovat a zajistit, aby to byl platný řetězec v daném kódování
[3] ať mě nikdo nechytá za slovo: i tam samozřejmě lze narazit na problémy, ale to už je dané spíš složitostí Unicodu jako takového – pořád je na tom Java řádově lépe než C++
A co takovýTohle ale nesouvisí s kódováním UTF-8 vs UTF-16. Pro výpočet šířky znaku (třeba "tradičně" pomocíprintf()
a zarovnávání textu pod sebe? Ono sice aniwprintf()
nefunguje vždy1, ale většinou ano – zatímcoprintf()
sestd::string
/char
se rozsype i na naprosto základních znacích (např. čeština s diakritikou) kvůli tomu, že ty znaky jsou vícebajtové.
wcwidth()
) stejně potřebuješ iterovat znaky v UTF-32. Všimni si, že wcwidth()
implementace podporující korektně celý rozsah Unicode počítají s 32-bitovým wchar_t
, potažmo UTF-32. V zásadě by tam měl být spíš nějaký odpovídající integer. Ten typ wchar_t
jakož i vlastně název funkce jsou matoucí, resp. pozůstatek historie.
Zkus si třeba jak ti to funguje s tímto stringem, kde znaky by měly mít šířku 1.
Kromě toho jsem už tolikrát zažil špatně napsané programy, které implicitně předpokládaly nějaké kódování a mršily data... takže za správný postup považuji explicitní konverze resp. měl bys to mít pod kontrolou a vědět, v jakém kódování je vstup, v jakém výstup… a typicky to vede na konverzi na nějakou interní reprezentaci.Ano, ale pointa je, že interní reprezentace pomocí wide stringů není o nic lepší než pomocí narrow stringů - ani jedno nepodporuje Unicode. Oboje jsou bloby obecných dat.
Zlatá Java, kde je prostě String
a tuhle třídu používají všechny knihovny.
Co je to platné, když ta třída String
nezaručuje validitu Unicode obsahu a může klidně obsahovat nevalidní data? Jaký je kvalitativní rozdíl mezi std::string
a Java String
? Obojí je shluk random bajtů, resp. to druhé je shluk random 16-bitových hodnot, které mohou a nemusí být validní Unicode. Ten rozdíl je pouze kvantitativní: Java String většinou obsahuje validní Unicode. Což je lepší nebo horší, podle toho, jak se na to díváš.
Máš nějaký zvláštní důvod používat wide stringy? Smysl by to mělo na Windows, které interně používají UTF-16. Většina linuxových terminálů používá buď UTF-8 nebo nějaké single-byte kódování. Ušetříš si tím ty konverze tam a zpět.
viz #37
Dál mi přijde trochu zvláštní mít exit code ve výjimce. Pokud potřebuješ vracet exit cody jiné než EXIT_SUCCESS a EXIT_FAILURE, asi bych je necpal přímo do té výjimky ale podle typu výjimky, co probublá až nahoru rozhodl, co se má vrátit.
Tohle jsem vykopíroval z jiného svého projektu, kde se to jmenuje CLIException a ty chybové kódy jsou z nějakého číselníku, který jim dává význam. Souhlasím, že by to bylo čistější řešit přes nějakou hierarchii tříd a z ní odvozovat ty návratové kódy, ale nechtělo se mi s tím v C++ dělat, takže jsem to trochu zjednodušil.
Na parsování argumentů z příkazové řádky existuje getopt() a spol.
O getopt()
vím, ale přijde mi, že by mi nijak nepomohlo – já totiž nepoužívám krátké varianty, ani nepotřebuji brát -abc
jako ekvivalent -a -b -c
nebo --arg=param
jako --arg param
. A ta práce, která zbude spočívá v konverzi pole řetězců na stromovou strukturu objektů a to už je specifické pro každou aplikaci a žádná knihovna s tím nepomůže (dovedu si představit elegantní deklarativní řešení pomocí anotací v Javě).
Místo push_back se s C++11 a výše doporučuje používat emplace_back.
Dík, vyzkouším.
Herb Sutter na téma extenzivního používaní auto měl na CppConu přednášku, skoro mám dojem, že to byla tahle. Pro jednoduché typy se to zdá jako zbytečnost ale jakmile budeš mít kód jako tohle
Na auto
si bohužel často vylámou zuby editory/IDE a přestane fungovat napovídání, takže to už tam raději uvedu typ explicitně. Někdy používám aliasy pomocí using
, kterými si nějaký složitější/generický typ pojmenuji nějak lépe. S tím napovídání nemá problém.
V případě, že se volá konstruktor bez argumentů, považuje se za idiomatický zápis str::string s{}.
V čem je to lepší? Podle Effective modern C++ by zápisy Abc a;
i Abc a{};
měly zavolat výchozí konstruktor. Ty závorky mi tam přijdou navíc.
V tom případě ti doporučuji použít lepší IDE :) Správné našeptávání pro C++11 a výše je náročnější na schopnost překladače pochopit psaný kód. Nový KDevelop nebo QtCreator s Clang Code Modelem s tím problém nemají a zvládají spoustu dalších věcí, např. online statickou analýzu apod. Osobně bych se v NetBeans s C++ asi nenervoval...Na
auto
si bohužel často vylámou zuby editory/IDE a přestane fungovat napovídání, takže to už tam raději uvedu typ explicitně. Někdy používám aliasy pomocíusing
, kterými si nějaký složitější/generický typ pojmenuji nějak lépe. S tím napovídání nemá problém.
Výhoda je v té idiomatičnosti, tedy že volání konstruktoru vypadá vždycky stejně. Mišmaš ze starého C++, kdeV případě, že se volá konstruktor bez argumentů, považuje se za idiomatický zápis str::string s{}.V čem je to lepší? Podle Effective modern C++ by zápisy
Abc a;
iAbc a{};
měly zavolat výchozí konstruktor. Ty závorky mi tam přijdou navíc.
Abc x;
i Abc x(1, 2, 3);
volá konstruktor je trochu rušivý. V C++11 lze zavolat konstruktor i takto, je-li znám typ, který se má vrátit.
class Dog { public: Dog(std::string breed, float age); }; Dog getPuppy() { return {"Dachshund", 0.3}; }
V tom případě ti doporučuji použít lepší IDE :) Správné našeptávání pro C++11 a výše je náročnější na schopnost překladače pochopit psaný kód. Nový KDevelop nebo QtCreator s Clang Code Modelem s tím problém nemají a zvládají spoustu dalších věcí, např. online statickou analýzu apod. Osobně bych se v NetBeans s C++ asi nenervoval...
Až na to, že Netbeans napovídají lépe než QtCreator (aspoň ta verze, která je v Ubuntu LTS). A to i když to srovnávám s Netbeans 8.2 z roku 2016.
Další věc je integrace s verzovacími systémy, kterou nikdo lepší než Netbeans AFIK nemá.
Asi zkusím tu novou verzi, ale primárně se snažím používat balíčky dostupné v rámci distribuce.
VCS nemůžu posoudit, protože v NB jsem ji snad nikdy nepoužíval. QtCreator se s běžnými VCS integruje taky, zda lépe či hůře než NB fakt netuším...
Za zásadní vlastnost považuji to, že mi Netbeans barevně označují řádky, které byly změněny/přidány/smazány oproti poslední verzi a umožňují vracet jednotlivé změny. Prohlížeč historie se taky hodí (to dost používám, když dělám revize). Jinak to kombinuji s příkazovou řádkou a třeba hg status
a hg commit
dělám často raději tam, ale na některé věci je GUI nenahraditelné a příkazová řádka mu nemůže konkurovat.
Tak jsem zapnul ten plug-in v QtCreatoru a napovídá o dost líp. Akorát mi tam vadí, že šířka toho vyskakovacího okna není pevně daná podle nejdelší metody, ale dynamicky se mění podle toho, jak dlouhé názvy to zrovna napovídá, takže to při listování všelijak poskakuje. Taky tam chybí dokumentace, alej jinak celkem OK.
U těch Netbeans mi teď funguje i to auto
– správně to odvodí typ a napovídá. Dřív to někdy zlobilo. Mám podezření, že to občas najde nějakou jinou verzi standardní knihovny nebo to nějak špatně vyhodnotí některá makra, což pak má za následek, že to určité věci přestane napovídat.
P.S. samozřejmě že nejde jen o napovídání, ale i o podtrhávání chyb v kódu – oboje pracuje nad stejným modelem.
No, já jsem se k tomuhle vláknu dostal naneštěstí až takhle pozdě, takže už další odpověď asi nikoho nezajímá. Ale mě zase nezajímá, že to nikoho nezajímá, takže si tu zkrátka uprdnu následující:
#include <iostream> #include <string> #include <errno.h> #include <fcntl.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> namespace { class FileOverwriter { public: FileOverwriter(const std::string& pathname) : path{pathname}, fd{[this]() { const int result{open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)}; if (result == -1) throw errno; std::cerr << "opened '" << path << "'\n"; return result; }()} {} FileOverwriter(FileOverwriter&& old) : path(old.path), fd(old.fd) { old.fd = -1; } ~FileOverwriter() { if (fd != -1) { if (close(fd) == -1) { std::cerr << strerror(errno) << std::endl; } std::cerr << "still owning '" << path << "', closing it for real\n"; } else { std::cerr << "no longer owning '" << path << "', not closing\n"; } } FileOverwriter& operator<<(const std::string& what) & { write_to_fd(what); return *this; } const FileOverwriter& operator<<(const std::string& what) const& { write_to_fd(what); return *this; } FileOverwriter operator<<(const std::string& what) && { write_to_fd(what); return std::move(*this); } private: void write_to_fd(const std::string& what) const { const char* bufpos = what.c_str(); size_t to_write = what.size(); for (;;) { const ssize_t written = write(fd, bufpos, to_write); if (written == -1) throw errno; to_write -= written; if (!to_write) break; bufpos += written; } } const std::string path; int fd; }; void WriteFooterAndClose(FileOverwriter o) { o << "this is at the end\n"; } FileOverwriter WriteSomethingAndGiveItBack(FileOverwriter o) { o << "this is somewhere in the middle\n"; return o; } void JustWriteBlahBlah(const FileOverwriter& o) { o << "blah\n" << "blah\n"; } } // namespace int main() try { // A few std::move() experiments on /tmp/first. FileOverwriter o{"/tmp/first"}; o << "o says blah\n"; FileOverwriter p{std::move(o)}; p << "p says blah\n"; FileOverwriter q{WriteSomethingAndGiveItBack(std::move(p))}; q << "q says blah\n"; WriteFooterAndClose(std::move(q)); // A somewhat temporary FileOverwriter on /tmp/second. FileOverwriter("/tmp/second") << "cheche\n" << "blabla\n"; // Full R-value crazinesss on /tmp/third. WriteFooterAndClose( WriteSomethingAndGiveItBack(FileOverwriter("/tmp/third") << "first line\n" << "second line\n") << "one more line" << "penultimate line; the end is near\n"); // To also check the const-reference case (on /tmp/forth)... FileOverwriter r("/tmp/forth"); JustWriteBlahBlah(r); // And we don't std::move `r` anywhere; let it die naturally. return 0; } catch (int err) { std::cout << strerror(err) << std::endl; return 1; }
Tohle^^^ "obaluje" soubor otevřený POSIXovým stylem pro zápis a zavře ho to jenom jednou, i přes různé předávání do/z funkcí a operátorů na všechny možné způsoby a strany.
Pro (některé) anonymy musím ještě zdůraznit, nejlépe desetkrát, že std::ofstream
jsem nepoužil jednoduše proto, že zadání znělo "jak obalovat věci z C v C++", nikoliv "jak zapsat soubor v C++". A soubor se zápisem je prostě jenom náhodný příklad; mohlo to být cokoliv jiného, co se v POSIXu dá alokovat.
Hnusný anonym.
Přesně kvůli ubohým existencím jako ty je ABCLinuxu o dost horší, než by mohlo být.
ABCLinuxu, bylo by možné už konečně systematicky zakázat takováto anonymní hovna???
Ano, bylo! Protože když nedávno jedno z těchto hoven zaplevelilo všechny poradny a diskuse, najednou bylo možné změnit pár zdrojáků a naimplementovat captchu v podstatě za víkend. Dokonce i pár trestních oznámení tam viselo ve vzduchu — až jsem si tenkrát otevřel popcorn ke sledování, co bude dál.
Bezva. No a nešlo tenkrát už konečně odstranit anonymní přístup úplně? Šlo. Proč zůstal zachovaný, to fakt nechápu.
Jďy už přykladem, hovynko anonymňy.
Tak ho nekrm a je to.
Zakázat přispívání všem nepřihlášeným kvůli pár idiotům mi přijde hloupé.
Tak ať on nekrmí mě! Taky jsem troll, nemůžu si pomoct.
To přetěžování operátorů je dobrý nápad, vím, že se s tím dají dělat hezké věci… ale ještě jsem na to neměl odvahu :-)
Přetěžování operátorů může být hezký i ošklivý nápad a u tohoto příkladu nemá v podstatě na nic vliv. Místo operátor<<(...)
se to klidně mohlo jmenovat write_somethig(...)
.
Jediné, co write_something(...).write_something(...)
nedokáže přesně napodobit, je operátor asociativní zprava. Ale to není případ
<<
. (Háček celého přetěžovaní je taky v tom, že sice (částečně) ovlivní význam operátorů, ale nic nezmůže stran jejich priority a směru vyhodnocování. Tedy, alespoň ne v C++.)
Tiskni
Sdílej:
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.