Articolul 27 nu abuzează de tip de conversie - utilizarea eficientă a c

Regula 27: Nu conversie de tip suprasolicitarea

Normele C ++ sunt proiectate să funcționeze corect cu tipuri era imposibil. În teorie, dacă programul compilează fără erori, înseamnă că nu încearcă să efectueze operații nesigure sau obiecte lipsite de sens. Aceasta este o garanție valoroasă. Nu este nevoie să-l abandoneze.

Din păcate, de conducere tipuri de sisteme de by-pass. Și aceasta poate duce la diverse probleme, dintre care unele sunt ușor de recunoscut, iar unele - este extrem de dificil. Dacă veniți la C ++ din lumea C, Java sau C #, etok ia notă, pentru că în aceste limbi în tipurile de mai sus de multe ori există o necesitate, iar acestea sunt mai puțin periculoase decât în ​​C ++. Dar, C ++ - acest lucru nu este C. Nu este Java. Acest lucru nu este C #. În aducerea acestei limbi - înseamnă că doriți să fie tratate cu respectul cuvenit.

Să începem cu tipurile de sondaj operatorii exprimate de sintaxă, pentru că există trei moduri diferite de a scrie același lucru. Reducerea de stil C arată astfel:

(T), expresie // da expresie de tip T

Sintaxa de conducere funcțională este:

T (expresie) // da expresie de tip T

Între aceste două forme nu există nici o diferență semnificativă, pur și simplu paranteze sunt aranjate în mod diferit. Eu numesc aceste forme de fantome în stil vechi.

C ++ introduce, de asemenea, patru noi forme de piese turnate (de multe ori numite fantome în stilul C ++):

Fiecare dintre ele are propria semnificație:

• const_cast folosit în mod obișnuit pentru a respinge un obiect constant. Nici un alt exprimate în C ++ stilul nu permite acest lucru;

• dynamic_cast este folosit în principal pentru a efectua „în condiții de siguranță Downcasting» (downcasting). Acest lucru permite operatorului să determine dacă obiectul aparține unei ierarhii moștenire de tip dată. Aceasta este singura conducere, care nu poate fi realizată folosind sintaxa veche. Aceasta este, de asemenea, singurul aducțiune care pot necesita cheltuieli apreciabile în timpul execuției (în detaliu mai târziu);

• reinterpret_cast concepute pentru fantome de nivel scăzut, care generează rezultate dependente de implementare (de exemplu, non-portabile), cum ar fi aducerea pointer la int. Dincolo de codul de nivel scăzut, o astfel de reducere ar trebui să fie utilizate rar. L-am folosit în această carte doar o singură dată, atunci când se discută scrierea unui repartitor de depanare (vezi regula 50);

• static_cast poate fi utilizat pentru conversii de tip explicite (de exemplu, la un non-const obiecte constante (ca regula 3), int să se dubleze și m. P.). Acesta poate fi, de asemenea, utilizat pentru a realiza transformarea inversă (de exemplu, indicii * goale tastat indicii, indicii pentru clasa de bază la indicatorul derivat). Dar aduce un obiect constant la non-constantă, acest operator nu poate (acest patrimoniu const_cast).

Aplicarea reducerilor în stil vechi este perfect legitim, dar noua formă este de preferat. În primul rând, ele sunt mult mai ușor de a găsi codul (pentru oameni, cât și pentru un instrument ca grep), care simplifică procesul de căutare în codul de locurile în care sistemul de tastare este în pericol. În al doilea rând, scopul mai strict specializate ale fiecărui operator de distributie permite compilatoare pentru a diagnostica erorile lor de utilizare. De exemplu, dacă încercați să scapi de constanței folosind orice operator turnat in C ++ stil, cu excepția const_cast, atunci codul nu va compila.

Eu folosesc un stil vechi exprimate doar când vreau să sun constructor explicit pentru a trece un obiect ca parametru funcției. De exemplu:

explicit Widget (int size);

void doSomeWork (Const widget w);

doSomeWork (Widget (15)); // Crearea widget din int

// o funcție de aducere

doSomeWork (static_cast (15)); // Crearea widget din int

// pentru a aduce stilul C ++

Dar crearea deliberată a unui obiect nu „simt“ ca turnat, astfel încât în ​​acest caz, este, probabil, mai bine să se utilizeze o reducere funcțională în loc de static_cast. Oricum, codul care duce la un accident, de obicei pare destul de rezonabil, atunci când scrieți, așa că cel mai bine este de a ignora sentimentele și utilizați întotdeauna fantome în noul stil.

Mulți programatori cred că exprimate doar spune compilatorului că un tip ar trebui sa fie tratat ca un prieten, dar ele sunt greșite. Tipul de conversie de orice fel (atât explicite de conducere și implicit efectuate de compilator), de multe ori duce la apariția codului executat în timpul rulării. Luați în considerare acest exemplu:

double d = static_cast (x) / y; // x divizare pentru y folosind

// divizare în virgulă mobilă

Aducerea int x pentru a dubla aproape sigur generează un cod executabil, pentru că în cele mai multe arhitecturi reprezentarea internă a unui int este diferit de dubla reprezentare. Dacă acest lucru este că nu deosebit de surprins, dar uita-te la următorul exemplu:

clasă derivată: Baza publică;

Baza * = pb d; // conversie implicită derivate *

Aici, am creat doar un pointer catre clasa de baza la un obiect derivat, dar uneori punctul de două indicii nu este pe aceeași. În acest caz, în timpul executării unui pointer derivate * adaugă un offset pentru a obține valoarea corectă a bazei * pointerul.

Lucru interesant despre fantome, - în faptul că este ușor de a scrie cod care arată corect (și poate fi corect în alte limbi), dar de fapt nu este corect. De exemplu, multe cadre pentru dezvoltarea de aplicații necesită ca funcțiile membre virtuale definite în clasa derivată, primul apel funcția corespunzătoare clasei de bază. Să presupunem că avem o fereastră clasă de bază și o clasă derivată din ea SpecialWindow, și în ambele functii virtuale definite onResize. presupunem că onResize de SpecialWindow va provoca prima onResize de la fereastra. punerea în aplicare următor arată bine, dar, de fapt greșit:

fereastra de clasă

virtuale void onResize () // implementare în baza onResize

Clasa de SpecialWindow: Fereastra publică

onResize void virtuale ()

static_cast (* acest) .onResize (); // într-o clasă derivată;

// turnat * acest lucru Window,

// apoi solicită onResize lui;

// acest lucru nu este de lucru!

// executie specifice

> // SpecialWindow parte onResize

Am subliniat în acest cod exprimate. (Acest lucru este de a aduce într-un stil nou, dar utilizați stilul vechi nu se schimba nimic.) Așa cum era de așteptat, acest lucru duce la * tipul de fereastră. Prin urmare, fac apel la onResize rezultate într-o fereastră apel :: onResize. Asta e doar această funcție nu va fi invocat pentru obiectul curent! In mod neasteptat, nu-i așa? În schimb, operatorul turnat va crea o copie nouă, temporară a clasei de bază * acest lucru și poate cauza onResize pentru această copie! Codul de mai sus nu va cauza Window :: onResize pentru obiectul curent și apoi efectuează o acțiune specifică SpecialWindow - acesta îndeplinește fereastra :: onResize pentru o copie a clasei de bază a obiectului curent înainte de a efectua acțiuni specifice SpecialWindow pentru obiect. Dacă fereastra :: onResize modifică obiectul (care este destul de posibil, ca onResize - funcția de membru nu este constantă), obiectul curent nu vor fi modificate. În schimb, acesta este modificat copie a acestui obiect. Cu toate acestea, în cazul în care SpecialWindow :: onResize modifică un obiect, acesta va fi modificat este obiectul curent. Ca urmare, obiectul curent este într-o stare inconsistentă deoarece o modificare a părții care aparține clasei de bază, nu va fi executat, și modificarea unei părți care aparține clasei derivate va fi.

Solutia este de a elimina exprimate, înlocuindu-l cu ceea ce ai avut în minte. Nu este nevoie să efectueze trucuri cu compilator, determinându-l să interpreteze * acest lucru ca un obiect de clasă de bază. Doriți să apelați versiunea clasa de baza de onResize pentru obiectul curent. Deci, se procedează după cum urmează:

Clasa de SpecialWindow: Fereastra publică

onResize void virtuale ()

Fereastra :: onResize (); // Fereastra de apelare :: onResize la * acest

Exemplul de mai sus demonstrează, de asemenea că, de îndată ce vă simțiți dorința de a efectua o distributie, acesta este un semn că s-ar putea fi pe pista greșită. Acest lucru este valabil mai ales operatorul dynamic_cast.

Înainte de a intra în detaliile dynamic_cast, este de remarcat faptul că cele mai multe implementări ale activității operatorului este destul de lent. Astfel, cel puțin una dintre implementările actuale ale claselor bazate pe un nume de comparare a reprezentat șiruri. Dacă faci dynamic_cast clasă de obiecte aparținând unei ierarhii cu adâncimea unică moștenire de patru niveluri, fiecare apel către dynamic_cast într-o astfel de implementare poate costa patru apeluri strcmp pentru a compara nume de clasă. Pentru o ierarhie mai profundă sau una în care există moștenire multiplă, această operațiune ar fi chiar mai costisitoare. Există motive, din cauza cărora unele implementări acest mod de lucru (pentru că trebuie să sprijine legarea dinamică). Astfel, în plus față de vigilența în ceea ce privește turnarea de tip, în principiu, ar trebui să fie deosebit de sceptici atunci când vine vorba de utilizarea dynamic_cast în program, pentru care performanța este în primul rând.

Originea - folosesc recipiente de stocare pointer (de multe ori un „inteligent“, de obicei 13 cm.) Pe obiectele însele derivate clase, în timp ce înlăturând nevoia de a manipula aceste obiecte prin interfețe de clasă de bază. De exemplu, în cazul în care numai SpecialWindow suporta pâlpâire (intermitent) în ierarhia fereastra noastră / SpecialWindow, în schimb:

typedef // vezi. Regula 13

std :: vector> VPW; // pentru tr1 :: shared_ptr