Érdekes

C++ Stílusú és Technika GYIK

Eredeti Bjarne Stroustrup az http://www.stroustrup.com/bs_faq2.html

Ezek a kérdések a C++ Stílus és Technika, hogy az emberek azt kérdezik gyakran. Ha van jobb kérdése vagy észrevétele a választ, nyugodtan e-mailt szerző Bjarne Stroustrup (bs a cs dot tamu dot edu). Kérjük, ne feledje, hogy nem töltöm minden időmet javítása a honlapok.

Én hozzájárult ahhoz, hogy az új, egységes, isocpp.org C++ GYIK által fenntartott, C++ Alapítvány, amelynek én vagyok a rendező. A fenntartó az e GYIK valószínűleg inkább szórványosan.

További általános kérdések, lásd az általános GYIK.

A terminológia, fogalom, lásd a C++ szójegyzék.

Kérjük, vegye figyelembe, hogy ezek csak egy gyűjteménye, kérdések, válaszok. Ezek nem helyettesítik a gondosan kiválasztott sorozat példák, magyarázatok, mint találni egy jó tankönyv. Sem kínálnak részletes, pontos előírások, mint megtalálni a referencia-kézikönyv, vagy a standard. Lásd Tervezési az Evolúció a C++ a kérdéssel kapcsolatban, hogy a tervezés a C++. Lásd C++ Programozási Nyelv kérdése van a használata a C++, illetve a standard könyvtár.


Hogyan tudom írni ezt a nagyon egyszerű program?

Gyakran, főleg az elején félév, kapok egy csomó kérdést, hogyan kell írni egy nagyon egyszerű program. Általában az a probléma, hogy lehet megoldani, hogy olvastam egy pár számot, de valamit csinálni velük, írj egy választ. Itt van egy minta program azt csinálja, hogy:

 

Itt van néhány megfigyelések arról, hogy ez a program:

  • Ez egy Szabvány ISO C++ program segítségével a standard könyvtár. Standard könyvtár létesítmények bejelentett namespace std fejléc nélkül .h utótag.
  • Ha akarsz összeállítani egy Windows gépet, meg kell fordítani, mint egy “konzol alkalmazás”. Ne feledje, hogy a forrás fájlt .cpp utótag vagy a fordító talán úgy gondolja, hogy ez a C (nem C++) forrás.
  • Igen, a main() függvény egy int.
  • Olvasás a szabványos vector garantálja, hogy nem túlcsordulás egy tetszőleges puffer. Olvasás egy tömb, anélkül, hogy egy “buta hiba” túl van a képessége, hogy teljes kezdő – mire megkapja, hogy igaz, már nem teljesen kezdő. Ha kétség merül fel ez az igény, javaslom elolvasni a “Tanulás Szabványos C++ mint egy Új Nyelv”, amely letölthető a kiadványok listáját.
  • A !cin.eof() egy teszt a folyam formátum. Konkrétan a vizsgálatok, hogy a hurok véget ért a megállapítás a fájl végét (ha nem, akkor nem kap bemeneti várható típusú/formátumú). További információkért, keresd fel a “patak állam” a C++ tankönyve.
  • Egy vektor tudja a méretét, így nem kell számolni elemek.
  • Igen, tudom, hogy én is kijelentem, én egy vector<double>::size_type inkább, mint a sima int, hogy csendes figyelmeztetések valami hiper-gyanús fordítók, de ebben az esetben,úgy vélem, hogy túl pedáns, zavaró.
  • Ez a program nem tartalmaz explicit memória kezelése, nem szivárog memória. Egy vektor nyomon követi a memóriát használ, hogy tárolja az elemeket. Ha egy vektor-nak több memória elemek, oszt többet; ha egy vektor megy ki a hatálya, felszabadítja a memóriát. Ezért a felhasználónak nem kell az érintett a elosztása, illetve deallocation memória vektor elemeit.
  • olvasás a szálakat, Hogyan olvasson egy string bemenet?.
  • A program véget ér olvasás bemenet, amikor látja, hogy “end of file”. Ha a program futtatása a keybord egy Unix gép “fájl végéig” Ctrl-D. Ha a Windows gép, hogy azért, mert egy hiba, nem ismer fel egy fájl végéig karakter, lehet, hogy inkább ez kissé bonyolultabb verzió a program, hogy lezárja a bemenetet a szót, hogy “vége”:
	#include<iostream>
	#include<vector>
	#include<algorithm>
	#include<string>
	using namespace std;

	int main()
	{
		vector<double> v;

		double d;
		while(cin>>d) v.push_back(d);	// read elements
		if (!cin.eof()) {		// check if input failed
			cin.clear();		// clear error state
			string s;
			cin >> s;		// look for terminator string
			if (s != "end") {
				cerr << "format error\n";
				return 1;	// error return
			}
		}

		cout << "read " << v.size() << " elements\n";

		reverse(v.begin(),v.end());
		cout << "elements in reverse order:\n";
		for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

		return 0; // success return
	}

További példákat, hogyan kell használni a standard könyvtár nem egyszerű dolog, egyszerűen csak a “Túra a Standard Könyvtárban” című Fejezetben a TC++PL4.


Tudna ajánlani egy kódolási szabvány?

Igen: C++ Core Iránymutatás. Ez egy ambiciózus projekt, hogy irányítsák az embereket, hogy hatékony stílus, modern C++ biztosítása eszköz, hogy támogassa a szabályokat. Ez arra ösztönzi az embereket, hogy a C++, mint egy teljesen típus- es erőforrás biztonságos nyelvi teljesítmény csökkenése nélkül vagy hozzáadásával a bőbeszédűség. Vannak videók leírja, hogy az iránymutatás projekt.

A lényeg, hogy egy C++ kódolási szabvány célja szabályok használata a C++ egy adott célra egy adott környezetben. Ebből következik, hogy nem lehet egy kódolási szabvány minden használ, minden felhasználó számára. Egy adott alkalmazás (vagy cég, alkalmazási terület, stb.), jó kódolási szabvány jobb, mint a semmi kódolási szabvány. Másrészt, láttam sok példa bizonyítja, hogy egy rossz kódolási szabvány rosszabb, mint nem kódolási szabvány.

Ne használja a C kódolási szabványok (még akkor is, ha kissé átalakított C++) , ne használja tíz éves C++ kódolási szabványok (még akkor is, ha jó az idő). C++ nem (csak) C Szabványos C++ nem (csak) előre szabványok C++.


Miért kell a népnek.

Lehet, hogy van egy probléma a fordító. Lehet öreg, lehet, hogy telepítve tévesen, vagy a számítógép lehet, hogy egy antik. Én nem tudok segíteni, ilyen problémák.

Azonban valószínűbb, hogy a program, hogy megpróbálja lefordítani a rosszul tervezett, így összeállítása ez magában foglalja a fordító vizsgálva több száz fejléc fájlokat, valamint több tízezer sornyi kódot. Elvileg ez elkerülhető. Ha ez a probléma a könyvtár eladó design, ott nem sok mindent lehet csinálni (kivéve változó, hogy egy jobb könyvtár/eladó), de szerkezet a saját kód minimalizálása újbóli összeállítása után változik. Minták, amelyek jellemzően jobb, könnyebben karbantartható, tervek, mert a kiállítás jobb elkülönítésére vonatkozik.

Fontolja meg egy klasszikus példa arra, hogy az objektum-orientált program:

	class Shape {
	public:		// interface to users of Shapes
		virtual void draw() const;
		virtual void rotate(int degrees);
		// ...
	protected:	// common data (for implementers of Shapes)
		Point center;
		Color col;
		// ...
	};

	class Circle : public Shape {
	public:	
		void draw() const;
		void rotate(int) { }
		// ...
	protected:
		int radius;
		// ...
	};

	class Triangle : public Shape {
	public:	
		void draw() const;
		void rotate(int);
		// ...
	protected:
		Point a, b, c;
		// ...
	};

Az ötlet az, hogy a felhasználók manipulálni formák keresztül Shape nyilvános felület, implementers a származtatott osztályok (pl. Kör, Háromszög) részesedése szempontból a végrehajtás képviseli a védett tagok.

Három komoly problémák ezzel a látszólag egyszerű ötlet:

  • Nem könnyű meghatározni a megosztott szempontból a végrehajtás az, ami hasznos, hogy az összes származtatott osztály. Ezért a készlet védett tagok valószínű, hogy a szükséges változások, sokkal gyakrabban, mint a nyilvános felületen. Például, annak ellenére, hogy a “központ” vitathatatlanul érvényes koncepció az összes Shape, egy kellemetlen, hogy fenn kell a pont “center” egy Háromszög – a háromszög, több értelme van, számítsa ki az központjában, ha valaki kifejezi az érdeklődés.
  • A védett tagok valószínűleg attól függ, hogy a “végrehajtás” részleteket, hogy a felhasználók a Shape inkább nem függ. Például sok (legtöbb?) kód segítségével Alakú lesz logikailag független a definíció a “Színes”, de a jelenléte a Szín meghatározása a Shape valószínűleg szükséges a fordítás a header fájlok meghatározó az operációs rendszer fogalma szín.
  • Ha valami a védett rész változik, a felhasználók Shape kell fordítanod – annak ellenére, hogy csak implementers a származtatott osztályok férhetnek hozzá a védett tagok.

Így a jelenlét, a “hasznos információk, hogy eszközök” a base osztály is működik, mint a felület, hogy a felhasználók számára a forrás, az instabilitás a végrehajtás, hamis recompilation a felhasználói kódot (ha a végrehajtás információk változások), illetve a túlzott felvétele a fejléc fájlokat a felhasználói kód (mert a “hasznos információk, hogy eszközök” van szüksége, azokat a fejlécek). Ez néha úgy ismert, mint a “törékeny base class probléma.”

A nyilvánvaló megoldás az, hogy kihagyja a “hasznos információk, hogy implemeters” osztályok használt interfészek, hogy a felhasználók. Az, hogy a kapcsolódási pontok, tiszta felületek. Ez azt jelenti, hogy képviselje felületek, mint absztrakt osztályok:

	class Shape {
	public:		// interface to users of Shapes
		virtual void draw() const = 0;
		virtual void rotate(int degrees) = 0;
		virtual Point center() const = 0;
		// ...

		// no data
	};

	class Circle : public Shape {
	public:	
		void draw() const;
		void rotate(int) { }
		Point center() const { return cent; }
		// ...
	protected:
		Point cent;
		Color col;
		int radius;
		// ...
	};

	class Triangle : public Shape {
	public:	
		void draw() const;
		void rotate(int);
		Point center() const;
		// ...
	protected:
		Color col;
		Point a, b, c;
		// ...
	};

A felhasználók most szigetelt a változtatások végrehajtását, származtatott osztályok. Láttam ezt a technikát csökken építeni alkalommal rendelte el a magnitúdója.

De mi van, ha tényleg van néhány információ, ami közös, hogy az összes származtatott osztály (vagy egyszerűen csak több származtatott osztály)? Egyszerűen, hogy ezt az információt egy osztály származnak, a végrehajtás osztályok is:

class Shape {
	public:		// interface to users of Shapes
		virtual void draw() const = 0;
		virtual void rotate(int degrees) = 0;
		virtual Point center() const = 0;
		// ...

		// no data
	};

	struct Common {
		Color col;
		// ...
	};
		
	class Circle : public Shape, protected Common {
	public:	
		void draw() const;
		void rotate(int) { }
		Point center() const { return cent; }
		// ...
	protected:
		Point cent;
		int radius;
	};

	class Triangle : public Shape, protected Common {
	public:	
		void draw() const;
		void rotate(int);
		Point center() const;
		// ...
	protected:
		Point a, b, c;
	};

Miért van az, akkora, mint egy üres osztály nem nulla?

Annak érdekében, hogy a cím két különböző tárgyak más lesz. Ugyanezen okból az “új” mindig visszatér mutatók különböző tárgyakat. Fontolja meg:

	class Empty { };

	void f()
	{
		Empty a, b;
		if (&a == &b) cout << "impossible: report error to compiler supplier";

		Empty* p1 = new Empty;
		Empty* p2 = new Empty;
		if (p1 == p2) cout << "impossible: report error to compiler supplier";
	}

Van egy érdekes szabály, ami azt mondja, hogy egy üres base class nem kell képviseli külön byte:

	struct X : Empty {
		int a;
		// ...
	};

	void f(X* p)
	{
		void* p1 = p;
		void* p2 = &p->a;
		if (p1 == p2) cout << "nice: good optimizer";
	}

Ez az optimalizálás biztonságban lehet a leghasznosabb. Ez lehetővé teszi, hogy a programozó használata üres osztály képviseletére nagyon egyszerű fogalmak nélkül fölött. Néhány aktuális fordító biztosítja ezt az “üres base class optimalizálás”.


Miért van, hogy az adatok az osztályban nyilatkozatok?

Nem. Ha nem akarod, hogy az adatok egy felület, ne írd meg az osztály, amely meghatározza a felhasználói felület. Tedd a származtatott osztályok helyett. Lásd, Minek a népnek tart ilyen sokáig?.
Néha szeretnék képviselet adatok egy osztályban. Fontolja osztály összetett:

template<class Scalar> class complex {
	public:
		complex() : re(0), im(0) { }
		complex(Scalar r) : re(r), im(0) { }
		complex(Scalar r, Scalar i) : re(r), im(i) { }
		// ...

		complex& operator+=(const complex& a)
			{ re+=a.re; im+=a.im; return *this; }
		// ...
	private:
		Scalar re, im;
	};

Ez a típus tervezték, hogy egy beépített típus, valamint a képviseletre van szükség, a nyilatkozat, hogy lehetővé teszik, hogy valóban helyi objektumok (azaz a tárgyak, amelyek osztják a verem, nem pedig egy halom), hogy meggyőződjön a megfelelő inlining egyszerű műveletek. Valóban helyi tárgyak, inlining szükséges, hogy a teljesítmény komplex közel, hogy mi van, feltéve, hogy a nyelvek egy beépített összetett típus.


Miért tag funkciók nem virtuális alapértelmezés szerint?

Mert sok osztályok nem úgy kell használni, mint a bázis osztályok. Például, lásd osztály összetett.
Is, tárgyak egy osztályba, egy virtuális függvény szükség tér szükség, melyet a virtuális függvény hívása mechanizmus – jellemzően egy szó / objektum. Ez a felső jelentős lehet, útban az elrendezés kompatibilitás adatokat más nyelvek (pl. C es Fortran).

Lásd Tervezési mind az Evolúció, a C++ további tervezési logika.


Miért destructors nem virtuális alapértelmezés szerint?

Mert sok osztályok nem úgy kell használni, mint a bázis osztályok. Virtuális függvények csak akkor van értelmük, az osztályok azt jelentette, hogy jár, mint interfészek, hogy a tárgyak a származtatott osztályok (jellemzően kiosztott egy rakás valamint keresztül elérhető mutatók vagy referenciák).

Szóval, mikor kijelentem, hogy a destructor virtuális? Amikor az osztályban van legalább egy virtuális függvény. Miután a virtuális függvények jelzi, hogy az osztály célja, hogy jár, mint egy interfész, hogy a származtatott osztályok, de ha sikerül, egy tárgy, egy származtatott osztály is el lehet pusztítani, keresztül a mutató az alap. Például:

	class Base {
		// ...
		virtual ~Base();
	};

	class Derived : public Base {
		// ...
		~Derived();
	};

	void f()
	{
		Base* p = new Derived;
		delete p;	// virtual destructor used to ensure that ~Derived is called
	}

Volt Bázis destructor nem virtuális, Származtatott van destructor nem hívtak – a valószínűleg rossz hatások, mint például a források tulajdonában lévő Származtatott nem, hogy felszabadult.


Miért nem virtuális konstruktőri?

Egy virtuális hívás mechanizmus, hogy munkát adott részleges információ. Különösen a “virtuális” lehetővé teszi számunkra, hogy egy funkciót, hogy csak egy interfészek, nem pedig a pontos típusa a tárgy. Hozzon létre egy objektumot, hogy szükség van a teljes információt. Különösen, tudnod kell a pontos típusa, amit szeretne létrehozni. Következésképpen, a “hívás, hogy egy kivitelező” nem lehet virtuális.

Technikák segítségével egy indirekciónak, amikor kértem, hogy hozzon létre egy objektumot, gyakran utalnak úgy, mint “Virtuális konstruktőri”. Például, lásd TC++PL3 15.6.2.

Például, itt van egy technika generáló objektum megfelelő típus segítségével egy absztrakt osztály:

	struct F {	// interface to object creation functions
		virtual A* make_an_A() const = 0;
		virtual B* make_a_B() const = 0;
	};

	void user(const F& fac)
	{
		A* p = fac.make_an_A();	// make an A of the appropriate type
		B* q = fac.make_a_B();	// make a B of the appropriate type
		// ...
	}

	struct FX : F {
		A* make_an_A() const { return new AX();	} // AX is derived from A
		B* make_a_B() const { return new BX();	} // BX is derived from B
	};

	struct FY : F {
		A* make_an_A() const { return new AY();	} // AY is derived from A
		B* make_a_B() const { return new BY();	} // BY is derived from B
	};

	int main()
	{
		FX x;
		FY y;
		user(x);	// this user makes AXs and BXs
		user(y);	// this user makes AYs and BYs

		user(FX());	// this user makes AXs and BXs
		user(FY());	// this user makes AYs and BYs
		// ...
	}

Ez egy változata, amit gyakran nevezik “a gyár minta”. A lényeg az, hogy a user() teljesen elszigetelt a tudás, osztályok, mint AX es AY.


Mi egy tisztán virtuális függvény?

Egy tisztán virtuális függvény egy olyan funkció, hogy kell, hogy legyen rendelve egy származtatott osztály, nem kell meghatározni. Egy virtuális függvény kijelentette, hogy “tiszta” a kíváncsi “=0” szintaxis. Például:

	class Base {
	public:
		void f1();		// not virtual
		virtual void f2();	// virtual, not pure
		virtual void f3() = 0;	// pure virtual
	};

	Base b;	// error: pure virtual f3 not overridden

Itt, a Bázis egy absztrakt osztály (mert ez egy tisztán virtuális függvény), így nem tárgyak osztály Alap lehet közvetlenül teremtett: Bázis (részben) azt jelentette, hogy a base osztály. Például:

	class Derived : public Base {
		// no f1: fine
		// no f2: fine, we inherit Base::f2
		void f3();
	};

	Derived d;	// ok: Derived::f3 overrides Base::f3

Absztrakt osztályok mérhetetlenül hasznos felületek meghatározása. Tény, hogy egy osztály csak a tiszta virtuális függvények gyakran nevezik felületet.

Megadhatunk egy tisztán virtuális függvény:

	Base::f3() { /* ... */ }

Ez néha nagyon hasznos (nyújtson néhány egyszerű közös végrehajtási részlet a származtatott osztályok), de Base::f3() kell mindig felül lehet írni egy származtatott osztály.

Ha nem felülíró egy tisztán virtuális függvény egy származtatott osztály, hogy a származtatott osztály válik absztrakt:

	class D2 : public Base {
		// no f1: fine
		// no f2: fine, we inherit Base::f2
		// no f3: fine, but D2 is therefore still abstract
	};

	D2 d;	// error: pure virtual Base::f3 not overridden

Miért nem túlterhelése munka a származtatott osztályok?

Ezt a kérdést (a sok variáció), általában a rendszer által egy példa, mint ez:

	#include<iostream>
	using namespace std;

	class B {
	public:
		int f(int i) { cout << "f(int): "; return i+1; }
		// ...
	};

	class D : public B {
	public:
		double f(double d) { cout << "f(double): "; return d+1.3; }
		// ...
	};

	int main()
	{
		D* pd = new D;

		cout << pd->f(2) << '\n';
		cout << pd->f(2.3) << '\n';
	}

fog termelni, amely:

	f(double): 3.3
	f(double): 3.6

ahelyett hogy

	f(int): 3
	f(double): 3.6

az a néhány ember (tévesen), hogy kitalálta.

Más szóval, nincs túlterhelés felbontás között a D es B fordító úgy néz ki, a körét, D, találja meg az egyetlen funkció “dupla f(dupla)” nevezi. Nem zavar a (mellékelve) hatálya B. C++, nincs túlterhelés át scopes – származtatott osztály körök nem kivétel az általános szabály. (Lásd D&E vagy TC++PL3 a részletekért).

De mi van, ha azt akarom, hogy hozzon létre egy túlterhelés sorozatot az f() függvényt az alap, s a származtatott osztály? Ez könnyen megoldható, használja a nyilatkozat:

	class D : public B {
	public:
		using B::f;	// make every f from B available
		double f(double d) { cout << "f(double): "; return d+1.3; }
		// ...
	};

Add hogy a módosítás a kimenet

	f(int): 3
	f(double): 3.6

Ez, túlterhelés felbontás alkalmazták B a f() es D a f()  válassza ki a legmegfelelőbb f() hívást.


Használhatom az “új”, mint Java-ban?

Részben, de nem vakon, gyakran vannak kiváló alternatíva. Fontolja meg:

{
	cmplx z2 = z+d;	// c++ style
	z2 = f(z2);		// use z2

	cmplx& z3 = *new cmplx(z+d);	// Java style (assuming Java could overload +)
	z3 = f(z3);
	delete &z3;	
}

A clumbsy használja az “új” z3 szükségtelen lassan képest a köznyelvi használata egy helyi változó (z2). Nem kell, hogy használja az “új”, hogy hozzon létre egy objektumot, ha te is a “törlés” pontot, hogy a tárgy ugyanaz a hatálya alá; e egy tárgyat kell egy helyi változó.


Hívjak egy virtuális függvény a kivitelező?

Igen, de légy óvatos. Lehet, hogy nem várt. A kivitelező, a virtuális hívás mechanizmus le van tiltva, mert felülírja a származtatott osztályok még nem történt meg. A tárgyak épített a bázis, “bázis előtt származik”.

Fontolja meg:

	#include<string>
	#include<iostream>
	using namespace std;

	class B {
	public:
		B(const string& ss) { cout << "B constructor\n"; f(ss); }
		virtual void f(const string&) { cout << "B::f\n";}
	};

	class D : public B {
	public:
		D(const string & ss) :B(ss) { cout << "D constructor\n";}
		void f(const string& ss) { cout << "D::f\n"; s = ss; }
	private:
		string s;
	};

	int main()
	{
		D d("Hello");
	}

a program összeállítja produkál

	B constructor
	B::f
	D constructor

Megjegyzés: ne D::f. Képzeljük el, mi történne, ha a szabály volt, más szóval, hogy D::f() volt a neve a B::B (): Mert a kivitelező D::D() még nem volt, D::f() próbálja rendelni az érv, hogy egy nem inicializált string s. Az eredmény nagy valószínűséggel azonnali összeomlás.

Pusztítás kész “származtatott osztály előtt base osztály”, a virtuális függvények viselkednek, mint a konstruktőri: Csak a helyi meghatározásokat használnak – nem hívásokat készülnek, hogy a legfontosabb funkciók, hogy ne érjen a (elpusztult) származtatott osztály egy része az objektum.

További részletekért lásd a D&E 13.2.4.2 vagy TC++PL3 15.4.3.

Azt javasolták, hogy ez a szabály végrehajtási lelet. Ez nem így van. Sőt, az lenne a észrevehetően könnyebb, hogy végre a veszélyes szabály hívja virtuális függvények a konstruktőri pontosan úgy, ahogy a többi funkció. Ez azonban azt jelentené, hogy nem virtuális függvényt írásbeli támaszkodni invariants által létrehozott alap osztályok. Ez egy szörnyű rendetlenség.


Van egy “elhelyezés törlése”?

Nem, de ha kell írni lehet a sajátod.

Fontolja meg elhelyezés új használt tárgyat egy sor arenas

        class Arena {
        public:
                void* allocate(size_t);
                void deallocate(void*);
                // ...
        };

        void* operator new(size_t sz, Arena& a)
        {
                return a.allocate(sz);
        }

        Arena a1(some arguments);
        Arena a2(some arguments);

Tekintettel arra, hogy tudjuk írni

       X* p1 = new(a1) X;
        Y* p2 = new(a1) Y;
        Z* p3 = new(a2) Z;
        // ...

De hogyan tudjuk később törölni azokat a tárgyakat, helyesen? Az oka, hogy nincs beépített “elhelyezés törlés”, hogy megfeleljen elhelyezés új, hogy nincs általános, így biztosítva, hogy helyesen használják. Sem a C++ típus rendszer lehetővé teszi számunkra, hogy arra következtetnek, hogy a p1 pontot, hogy egy tárgyat juttatott, az Aréna a1. Egy mutató, hogy bármely X kiosztott bárhol lehet rendelni p1.

Előfordul azonban, hogy a programozó tudja, van egy megoldás:

        template<class T> void destroy(T* p, Arena& a)
        {
                if (p) {
                        p->~T();		// explicit destructor call
                        a.deallocate(p);
                }
        }

Most, hogy tudunk írni:

        destroy(p1,a1);
        destroy(p2,a2);
        destroy(p3,a3);

Ha egy Arena tartja számon, hogy milyen tárgyakat tart, akkor is írni destroy (), hogy meg tudja védeni magát a hibákat.
Az is lehetséges, hogy adjuk meg a megfelelő operator new() vagy operator delete() pár egy osztály hierarchia TC++PL(SE) 15.6. Lásd még D&E 10.4 vagy TC++PL(SE) 19.4.5.


Lehet megakadályozni, hogy az emberek származó osztályban?

Igen, de miért? Van két közös válaszok:

  • a hatékonyság érdekében: kerülje a funkció hívások hogy virtuális
  • a biztonság érdekében: annak érdekében, hogy az osztály nem használt bázis osztály (például, hogy biztos, hogy nem lehet másolni a tárgyak, félelem nélkül szeletelés)

A tapasztalat, a hatékonyság oka általában alaptalan a félelem. C++ virtuális függvény hívás, olyan gyorsan, hogy a valós világ használja az osztály célja, hogy a virtuális függvények nem termel mérhető run-time fejtermékek képest alternatív megoldások használata a hétköznapi funkció hívások. Vegye figyelembe, hogy a virtuális függvény hívása mechanizmus jellemzően csak akkor, ha hív keresztül egy mutató vagy referencia. Amikor a hívott függvény közvetlenül a megnevezett tárgy, a virtuális funkció osztály rezsi könnyen optimalizált el.

Ha erre valóban szükség van a “plafon” egy osztály-hierarchia, hogy elkerüljék a virtuális függvényt hív, felmerül a kérdés, hogy miért azok a funkciók, vagy virtuális az első helyen. Láttam már példát, ahol a teljesítmény szempontjából kritikus funkciók lett a virtuális ok nélkül, csak azért, mert “így szoktuk csinálni”.

A másik változata ez a probléma, hogyan lehet megelőzni levezetése logikus okok miatt, van megoldás a C++11. Például:

	struct Base {
		virtual void f();
	};

	struct Derived final : Base {	// now Derived is final; you cannot derive from it
		void f() override;
	};

	struct DD: Derived {// error: Derived is final

		// ...
	};

Az idősebb fordítók, akkor használjon egy kissé ügyetlen technika:

	class Usable;

	class Usable_lock {
		friend class Usable;
	private:
		Usable_lock() {}
		Usable_lock(const Usable_lock&) {}
	};

	class Usable : public virtual Usable_lock {
		// ...
	public:
		Usable();
		Usable(char*);
		// ...
	};

	Usable a;

	class DD : public Usable { };

	DD dd;  // error: DD::DD() cannot access
        	// Usable_lock::Usable_lock(): private  member

Miért nem C++ nyújt olyan tartályokban?

C++ standard könyvtár rendelkezik egy sor hasznos, statikusan típus-biztonságos, hatékony konténerek. Példák vektor, lista, majd a térkép:

	vector<int> vi(10);
	vector<Shape*> vs;
	list<string> lst;
	list<double> l2
	map<string,Record*> tbl;
	map< Key,vector<Record*> > t2;

Ezek a konténerek leírt minden jó C++ tankönyvek, előnyben kell részesíteni a több mint tömbök, “házi” konténerek, hacsak nincs jó oka van, hogy nem.

Ezek a konténerek homogén; ez azt jelenti, hogy tartsa elemek azonos típusú. Ha szeretné, egy konténer, hogy tartsa az elemeket a különböző típusokat, azt kell kifejezni, hogy akár uniós, vagy (általában sokkal jobb), mint egy konténer tanácsot, hogy egy polimorf típusú. A klasszikus példa:

	vector<Shape*> vi;	// vector of pointers to Shapes

Itt, vi tartani elemek bármilyen típusú származó Shape. Ez, vi homogén, hogy annak minden eleme Formák (hogy pontos legyek, mutatók Shape), valamint a heterogén abban az értelemben, hogy vi tartani elemek a sokféle Shape, mint például a Körök, Háromszögek, stb.

Ilyen értelemben minden tartály (minden nyelven) homogén, mert, hogy használja őket, hogy lennie kell egy közös felület elemei a felhasználók számára, hogy támaszkodni. Nyelvek, amelyek a konténerek tekinteni, olyan egyszerűen nyújt konténer elemei, hogy minden biztosít egy szabványos interfészen. Például a Java gyűjtemények nyújt konténerek (hivatkozásokat) Tárgyakat, használja a (közös) Objektum interfész, hogy fedezze fel a valódi típusú elem.

C++ standard könyvtár biztosítja a homogén konténerek, mert azok a legkönnyebb használni az esetek túlnyomó többségében, adja a legjobb compile-time error üzenetet, illetve nem ró felesleges run-time általános költségek.

Ha szüksége van egy heterogén tartály, C++, meg egy közös felületet, az a elemek, valamint egy tároló. Például:

class Io_obj { /* ... */ };	// the interface needed to take part in object I/O

vector<Io_obj*> vio;		// if you want to manage the pointers directly
vector< Handle<Io_obj> > v2;	// if you want a "smart pointer" to handle the objects

Ne dobd el a legalacsonyabb szintet, a végrehajtás részletes, kivéve, ha:

	vector<void*> memory;	// rarely needed

Egy jól jelzi, hogy már “túl alacsony” ez a kód lesz tele vet.

Használatával Any osztály, mint a Boost::Any, lehet egy alternatív egyes programok:

	vector<Any> v;

Miért a szabványos tartályban, így lassú?

Nem. Valószínűleg a “mihez képest?” egy több hasznos választ. Amikor az emberek panaszkodnak a standard könyvtár tartály, teljesítmény, én általában egy három valódi problémák (vagy az egyik a sok mítoszok, vörös hering):

  • Szenvedek másolás rezsi
  • Még szenvedni a lassú sebesség keresési táblázatok
  • A kéz-kódolt (tolakodó) listák sokkal gyorsabb, mint az std::list

Mielőtt próbálják optimalizálni, fontolja meg, hogy ha van egy valódi teljesítmény probléma. A legtöbb esetben küldött nekem, a teljesítmény probléma elméleti vagy képzeletbeli: Első intézkedés, akkor optimális, csak akkor, ha szükséges.

Nézzük meg ezeket a problémákat. Gyakran előfordul, hogy egy vector<X> lassabb, mint valaki speciális My_container<X> mert My_container<X> végre, mint egy konténer mutatók X. A standard tárolóedényeket tartsa példányban értékeket, majd másolja át egy értéket, ha a tartályba. Ez lényegében verhetetlen a kis érték, de lehet teljesen alkalmatlan a hatalmas tárgyakat:

	vector<int> vi;
	vector<Image> vim;
	// ...
	int i = 7;
	Image im("portrait.jpg");	// initialize image from file
	// ...
	vi.push_back(i);	// put (a copy of) i into vi
	vim.push_back(im);	// put (a copy of) im into vim

Most, ha portrait.jpg egy pár megabájt, illetve a Kép értéke szemantika (azaz a másolási feladatot, majd másolja építési másolatokat), akkor a vim.push_back(im) valóban drága. De — ahogy a mondás tartja — ha annyira fáj, csak ne tedd. Helyette, vagy használja a tartály kezeli, vagy egy konténer tanácsot. Például, ha a Kép a referencia-szemantika, a fenti kód kiváltaná csak a költségek egy copy konstruktor hívást, ami jelentéktelen ahhoz képest, hogy a legtöbb kép manipuláció szereplők. Ha egy osztály, mondjuk a Kép újra, nem másolat szemantika jó oka, egy konténer mutatók gyakran ésszerű megoldás:

	vector<int> vi;
	vector<Image*> vim;
	// ...
	Image im("portrait.jpg");	// initialize image from file
	// ...
	vi.push_back(7);	// put (a copy of) 7 into vi
	vim.push_back(&im);	// put (a copy of) &im into vim

Természetesen, ha használja a tanácsot, meg kell gondolni, erőforrás-gazdálkodás, de konténerek mutatók önmagukban hatékony, olcsó erőforrás kezeli (gyakran, kell egy tartály destructor törlése a “birtokolt” tárgy).

A második leggyakrabban előforduló valódi teljesítmény probléma az, hogy használja a map<string,X> egy nagy számú (string,X) párok. Térképek jól viszonylag kis tartályokban (mondjuk néhány száz, vagy néhány ezer elemű hozzáférést eleme egy térkép 10000 elemek ára körülbelül 9 összehasonlítás), hol kevésbé, mint az olcsó, ahol nem jó hash-függvény lehet kialakítani. Ha sok szálat, jó hash függvényt használja egy hash tábla. A unordered_map a standard bizottság Technikai Jelentés ma már széles körben elérhető, sokkal jobb, mint a legtöbb ember homebrew.

Néha, felgyorsítja a dolgokat segítségével (const char*,X) párok inkább, mint a (string,X) párok, de ne feledjük, hogy < nem lexicographical összehasonlítás a C stílusú karakterláncok. Továbbá, ha X nagyobb, előfordulhat, hogy a másolás probléma is (megoldani az egyik a szokásos módon).

Tolakodó listák lehet nagyon gyorsan. Azonban, hogy szükség van-e a listán: a vektor kompakt, ezért a kisebb, gyorsabb, sok esetben – még akkor is, ha beszúr, töröl. Például, ha logikusan van egy lista, néhány egész elemek, a vektor jelentősen gyorsabb, mint a list (lista). Is, tolakodó listák nem tudjuk tartani a beépített típusok közvetlenül (int nincs egy link tag). Szóval, tegyük fel, hogy tényleg szükségem van egy lista, hogy a kínálat egy linket területen minden elem típusát. A standard könyvtár lista alapértelmezés szerint végzi elosztási majd egy példányt minden egyes művelet beillesztése egy elem (deallocation minden egyes művelet eltávolítása elem). Az std::lista az alapértelmezett kiutaló, ez jelentős lehet. A kis elemeket, ahol a másolni fölött nem jelentős, fontolja meg egy optimalizált kiutaló. Használható egy kézzel készített tolakodó listák csak akkor, ha egy listából, majd az utolsó csepp teljesítményre van szükség.

Az emberek néha a költségek std::vector növekszik, fokozatosan. Nem úgy, hogy használt tartalék (), hogy optimalizálja a növekedés. Mérése után a kódot, majd többször is nehezen találni a teljesítmény előnyök a reserve() valós programok, abbahagytam a használatát, kivéve, ha ez szükséges ahhoz, hogy elkerüljük a iterator érvénytelenítése (ritka esetben a kód). Még egyszer: az intézkedés előtt optimalizálja.


Jelent az, hogy “barátom” sérti beágyazási?

Nem. A “barát” kifejezett mechanizmus lehetővé teszi a hozzáférést, mint a tagság. Nem (a szabvány megfelelő program) támogatás magad hozzáférést osztály módosítása nélkül a forrás. Például:

	class X {
		int i;
	public:
		void m();		// grant X::m() access
		friend void f(X&);	// grant f(X&) access
		// ...
	};

	void X::m() { i++; /* X::m() can access X::i */ }

	void f(X& x) { x.i++; /* f(X&) can access X::i */ }

Egy leírás a C++ védelem modell, lásd D&E sec 2.10, TC++PL sec 11.5, 15.3, C. 11.


Miért nem a kivitelező munkáját?

Ez egy olyan kérdés, hogy sok formája van. Például:

  • Miért a fordító másolni a tárgyak, ha én nem akarom?
  • Hogyan tudom kikapcsolni a másolás?
  • Hogyan állíthatom le implicit konverziót?
  • Hogy tudta ezt az int viszont egy komplex szám?

Alapértelmezés szerint egy osztály kap egy copy constructor egy másolási feladat, hogy az összes másolása elemek. Például:

	struct Point {
		int x,y;
		Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
	};

	Point p1(1,2);
	Point p2 = p1;

Itt kap p2.x==p1.x es p2.y==p1.y. Ez gyakran pontosan az, amit akarsz (pedig elengedhetetlen a C kompatibilitás), de figyelembe venni:

	class Handle {
	private:
		string name;
		X* p;
	public:
		Handle(string n)
			:name(n), p(0) { /* acquire X called "name" and let p point to it */ }
		~Handle() { delete p; /* release X called "name" */ }
		// ...
	};

	void f(const string& hh)
	{
		Handle h1(hh);
		Handle h2 = h1;	// leads to disaster!
		// ...
	}

Itt, az alapértelmezett másolási ad h2.name==h1.name es h2.p==h1.p. Ez katasztrófához vezet: mikor kilépünk f() a destructors a h1 es h2 alkalmazzák, valamint az objektum által mutatott h1.p es h2.p el kell hagyni kétszer.

Hogyan kerüljük ezt el? A legegyszerűbb megoldás, hogy a másolást megakadályozzák azzal, hogy a műveletek másolás magán:

	class Handle {
	private:
		string name;
		X* p;

		Handle(const Handle&);	// prevent copying
		Handle& operator=(const Handle&);
	public:
		Handle(string n)
			:name(n), p(0) { /* acquire the X called "name" and let p point to it */ }
		~Handle() { delete p; /* release X called "name" */ }
		// ...
	};

	void f(const string& hh)
	{
		Handle h1(hh);
		Handle h2 = h1;	// error (reported by compiler)
		// ...
	}

Ha kell másolni, tudjuk persze, adjuk meg a másolás initializer a másolási feladatot, hogy adja meg a kívánt szemantika.

Most térjünk vissza a Point. Lényeg  a Point alapértelmezett másolási szemantika rendben van, a gond az, hogy a konstruktor:

	struct Point {
		int x,y;
		Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
	};

	void f(Point);

	void g()
	{
		Point orig;	// create orig with the default value (0,0)
		Point p1(2);	// create p1 with the default y-coordinate 0
		f(2);		// calls Point(2,0);
	}

Az emberek nyújt alapértelmezett érveket, hogy a kényelem használt eredeti, p1. Aztán, néhány meglepett az átalakítás 2 Point(2,0) a call of f(). A kivitelező vesz egyetlen érv határozza meg a konverzió. Alapértelmezés szerint ez egy implicit konverzió. Szükség egy ilyen átalakítás, hogy kifejezetten kijelentem, hogy a kivitelező kifejezett:

	struct Point {
		int x,y;
		explicit Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
	};

	void f(Point);

	void g()
	{
		Point orig;	// create orig with the default value (0,0)
		Point p1(2);	// create p1 with the default y-coordinate 0
				// that's an explicit call of the constructor
		f(2);		// error (attmpted implicit conversion)
		Point p2 = 2;	// error (attmpted implicit conversion)
		Point p3 = Point(2);	// ok (explicit conversion)
	}

Miért nem C++ mindkét mutató hivatkozásokkal?

C++ örökölt tanácsokat C, így nem tudtam távolítsa el őket, anélkül, hogy komoly kompatibilitási problémák. Referenciák hasznos, ha több dolgot, de a közvetlen oka, hogy én mutattam be őket a C++ volt, hogy támogassa üzemeltető túlterhelését. Például:

	void f1(const complex* x, const complex* y)	// without references
	{
		complex z = *x+*y;	// ugly
		// ...
	}

	void f2(const complex& x, const complex& y)	// with references
	{
		complex z = x+y;	// better
		// ...
	}

Általában, ha azt akarjuk, hogy mind a funkcionalitás mutatók, valamint a funkció hivatkozások, van szüksége, vagy két különböző típusú (mint a C++), vagy két különböző műveletek egyetlen típus. Például, egy egységes típusa van szükség mindkét művelet hozzárendelése a tárgy említett művelet hozzárendelése a referencia/mutatót. Ezt meg lehet tenni, ha külön szereplők (mint a Simula). Például:

	Ref<My_type> r :- new My_type;
	r := 7;			// assign to object
	r :- new My_type;	// assign to reference

Másik lehetőség, hogy számíthatok típus ellenőrzése (túlterhelés).Például:

	Ref<My_type> r = new My_type;
	r = 7;			// assign to object
	r = new My_type;	// assign to reference

Kell használni hívás érték, vagy hívjon hivatkozással?

Ez attól függ, mit akarsz elérni:

  • Ha meg szeretné változtatni az objektum telt el, hívd hivatkozással, vagy használjon egy pointer; pl. void f(X&); vagy void f(X*);
  • Ha nem akarod, hogy módosítsa a tárgy telt el, s ez nagy, hívja a const referencia; pl. void f(const X&);
  • Ellenkező esetben a hívás érték; pl. void f(X);

Mit jelent az, hogy “nagy”? Valami nagyobb, mint egy pár szót.

Miért akarnám megváltoztatni egy érv? Nos, gyakran kell, de gyakran van egy alternatív: készítsen egy új értéket. Fontolja meg:

	void incr1(int& x);	// increment
	int incr2(int x);	// increment

	int v = 2;
	incr1(v);	// v becomes 3
	v = incr2(v);	// v becomes 4

Azt hiszem, hogy egy olvasó, incr2() könnyebb megérteni. Ez, incr1() nagyobb valószínűséggel vezet, hibák, hibák. Szóval, inkább a stílus, hogy visszatér egy új érték felett az egyik, hogy módosítja az értéket, amíg a teremtés, majd másolja az új érték nem drága.

Szeretném megváltoztatni a vita, kell-e használni egy mutatót vagy egy referencia? Nem tudom, hogy egy erős logikus oka. Ha halad a “tárgy” (pl. egy null pointer) elfogadható, használata mutató értelme. A személyes stílusa az, hogy egy mutató, ha azt akarom, hogy módosítsa a tárgy, mert bizonyos kontextusokban, hogy könnyebb észrevenni, hogy a módosítás lehetséges.

Vegye figyelembe azt is, hogy egy call of valamely funkció lényegében egy call-by-reference az objektum, ezért gyakran használunk tag funkciókat, ha azt akarjuk, hogy módosítsa az értéket/állam egy tárgy.


Miért van az az “ez” nem egy referencia?

Mert az az “ez” be C++ (tényleg a C es Osztályok), mielőtt hivatkozások egészült ki. Is, úgy döntöttem, hogy “ez az”, hogy kövesse a Simula használata, sokkal inkább, mint a (később) Smalltalk használja a “egyéni”.


Mi a baj a tömbök?

A feltételek az időt, a teret, a tömb csak az optimális konstrukció eléréséhez sorozata objektumokat a memóriában. Ez azonban szintén egy nagyon alacsony szintű adatok szerkezetű, hatalmas esetleges visszaélések, valamint a hibák lényegében minden esetben vannak jobb alternatívák. A “jobb” könnyebb írni, könnyebb olvasni, kevésbé hajlamosak a hibára, vagy gyorsan.

A két alapvető problémák tömbök, hogy

  • egy tömb nem tudja saját méret
  • a neve egy tömb átalakítja a mutatót az első elem a legkisebb provokáció

Vegyünk néhány példát:

	void f(int a[], int s)
	{
		// do something with a; the size of a is s
		for (int i = 0; i<s; ++i) a[i] = i;
	}

	int arr1[20];
	int arr2[10];

	void g()
	{
		f(arr1,20);
		f(arr2,20);
	}

A második hívás firka az egész memóriát nem tartozik arr2. Természetesen, egy programozó általában a méret, igaz, de ez a plusz munka valaha oly gyakran, hogy valaki a hibát. Én inkább az egyszerűbb, tisztább verzió használata a standard könyvtár vector:

	void f(vector<int>& v)
	{
		// do something with v
		for (int i = 0; i<v.size(); ++i) v[i] = i;
	}

	vector<int> v1(20);
	vector<int> v2(10);

	void g()
	{
		f(v1);
		f(v2);
	}

Mivel a tömb nem tudom a méretét, nem lehet tömb feladat:

	void f(int a[], int b[], int size)
	{
		a = b;	// not array assignment
		memcpy(a,b,size);	// a = b
		// ...
	}

Újra, inkább vektor:

	void g(vector<int>& a, vector<int>& b, int size)
	{
		a = b;	
		// ...
	}

Másik előnye, vektor, hogy memcpy() nem a helyes elemek másolása konstruktőri, mint a szálakat.

	void f(string a[], string b[], int size)
	{
		a = b;	// not array assignment
		memcpy(a,b,size);	// disaster
		// ...
	}

	void g(vector<string>& a, vector<string>& b, int size)
	{
		a = b;	
		// ...
	}

A tömb egy fix méretű határozza meg a fordítási időben:

	const int S = 10;

	void f(int s)
	{
		int a1[s];	// error
		int a2[S];	// ok

		// if I want to extend a2, I'll have to change to an array
		// allocated on free store using malloc() and use realloc()
		// ...
	}

Kontraszt:

	const int S = 10;

	void g(int s)
	{
		vector<int> v1(s);	// ok
		vector<int> v2(S);	// ok
		v2.resize(v2.size()*2);
		// ...
	}

C99 lehetővé teszi a változó tömb határait a helyi tömbök, de azok VLA is megvannak a saját problémáik.

A módja annak, hogy a tömb nevét a “hanyatlás” ba mutató alapvető fontosságú, hogy a használata a C vagy C++. Azonban a tömb bomlás befolyásolja nagyon rosszul örökség. Fontolja meg:

	class Base { void fct(); /* ... */ };
	class Derived : Base { /* ... */ };

	void f(Base* p, int sz)
	{
		for (int i=0; i<sz; ++i) p[i].fct();
	}

	Base ab[20];
	Derived ad[20];

	void g()
	{
		f(ab,20);
		f(ad,20);	// disaster!
	}

A legutóbbi hívás, a Derived[] kezelik, mint a Base[] gombot, majd a subscripting nem működik megfelelően, ha sizeof(Derived)!=sizeof(Base) — amint lesz az esetben a legtöbb esetben az érdeklődés. Ha használt vektorok helyett, az hiba lenne fogott fordítási időben:

	void f(vector<Base>& v)
	{
		for (int i=0; i<v.size(); ++i) v[i].fct();
	}

	vector<Base> ab(20);
	vector<Derived> ad(20);

	void g()
	{
		f(ab);
		f(ad);	// error: cannot convert a vector<Derived> to a vector<Base>
	}

Úgy találom, hogy egy elképesztő számú kezdő programozási hibák C es C++ vonatkoznak felhasználása, tömbök.


Miért nem C++ egy végső kulcsszó?

Ez így is van, de ez nem olyan hasznos, mint gondolnád.


Kell használni NULL, vagy 0?

C++, a meghatározás, NULL a 0 nem, akkor csak esztétikai különbség. Én inkább elkerülni a makrók, így nem használja a 0-t. Egy másik probléma, NULL, hogy az emberek néha tévesen úgy vélik, hogy 0-tól különböző, és/vagy nem egész szám. A pre-szabványos kódot, NULL volt/van néha úgy határozzák meg, hogy valami alkalmatlan, ezért volt/van, hogy el kell kerülni. Ez kevésbé gyakori manapság.

Ha meg kell nevezni a null mutató, hívja nullptr; ezt úgy hívják, hogy a C++11. Aztán, “nullptr” lesz a kulcsszó.


Hogy vagy C++ tárgyak lefektetett memória?

Mint a C, C++ nem az határozza meg, elrendezés, csak szemantikai korlátok, hogy meg kell felelni. Ezért a különböző implementációk másképp mennek a dolgok. Sajnos, a legjobb magyarázat tudom, hogy van egy könyv, amit egyébként elavult, illetve nem írja le a jelenlegi C++ végrehajtás: A jegyzetekkel ellátott C++ Referencia Kézikönyv (általában nevezik a ARM – Annotated C++ Reference Manual). Ez diagramok a kulcs elrendezés példái. Van egy nagyon rövid magyarázat a Fejezet 2 a TC++PL.

Alapvetően, C++ konstrukciók tárgyak egyszerűen összefűző sub tárgyakat. Így

        struct A { int a,b; };

képviseli két ints egymás mellett

        struct B : A { int c; };

képviseli egy A, majd egy int; ez által három ints egymás mellett.

Virtuális függvények jellemzően által végrehajtott hozzátéve, mutató (a vptr), hogy minden objektum egy osztály virtuális függvények. Ez a pointer mutat a táblázat megfelelő funkciók (a vtbl). Minden osztály saját vtbl által megosztott minden objektum osztály.


Mi az értéke  i++ + i++?

Ez nem definiált. Alapvetően, a C es C++, ha elolvasod a változó kétszer egy kifejezés, hol is írtam, az eredmény nem definiált. Ne csináld ezt. Egy másik példa:

	v[i] = i++;

Kapcsolódó példa:

	f(v[i],i++);

Itt, az eredmény csak akkor van definiálva, mert a rend értékelése funkció az érvek nem definiált.

Miután a rend értékelés undefined azt állította, hogy a hozam jobban teljesítő kódot. Fordító is figyelmeztet az ilyen példák, amelyek jellemzően apró bogarak (vagy potenciális apró bogarak). Csalódott vagyok, hogy évtizedek után, a legtöbb fordító még nem figyelmeztetjük, így azt a munkát, hogy speciális, különálló, kihasználatlan eszközök.


Miért van néhány dolog maradt undefined C++?

Mert a gépek különböznek egymástól, mert a C bal sok minden nem definiált. Részletek, beleértve a kifejezések, hogy “nem definiált”, “meghatározatlan”, “végrehajtás meghatározott”, “jól formált”; lásd az ISO C++ szabvány. Vegye figyelembe, hogy mit jelentenek ezek a fogalmak eltérnek a meghatározása az ISO C szabványt, valamint a közös használat. Lehet kapni csodálatosan zavaros viták, amikor az emberek nem veszik észre, hogy nem mindenki osztozik meghatározások.

Ez egy helyes, ha nem kielégítő válasz. Mint a C, C++, az azt jelentette, hogy kihasználja a hardver közvetlenül, hatékonyan. Ez azt jelenti, hogy a C++ kell foglalkozni hardver szervezetek, mint például bit, bájt, szó, cím, számítások egész, lebegőpontos számítások, ahogy vannak egy adott gép, ahelyett, hogy hogyan lehet őket. Vegye figyelembe, hogy a sok “dolgot”, amit az emberek úgy neveznek, hogy “nem definiált” a tény, hogy a “végrehajtás meghatározott”, hogy mi lehet írni tökéletesen megadott kódot mindaddig, amíg nem tudjuk, hogy melyik gép fut. Méretű egész számok, illetve a kerekítési magatartás a lebegőpontos számítások ebbe a kategóriába esik.

Fontolja meg, amit talán a legismertebb, legtöbb hírhedt példa nem definiált viselkedést:

	int a[10];
	a[100] = 0;	// range error
	int* p = a;
	// ...
	p[100] = 0;	// range error (unless we gave p a better value before that assignment)

C++ ( es C) fogalmát tömb mutató közvetlen ábrázolása egy gép fogalmát memória címek, feltéve, nincs rezsi. A primitív műveletek mutató térkép közvetlenül gépi utasításokat. Különösen nem a tartomány ellenőrzése megtörtént. Csinál tartomány ellenőrzése róna költség szempontjából fut az idő, a kód mérete. C célja az volt, hogy outcompete közgyűlés kód operációs rendszerek feladatai, úgy, hogy a szükséges döntést. Is, C — ellentétben a C++ nincs ésszerű módja a beszámolási megsértése volt a fordító úgy döntött, hogy létrehoz a kód, hogy felismerjük: nincsenek kivételek C. C++ követte C okok miatt nem kompatibilis, mert a C++ ban is versenyt közvetlenül assembler (OS, beágyazott rendszerek, valamint néhány numerikus számítás területek). Ha szeretnéd tartomány ellenőrzése, használjon megfelelő ellenőrzött osztály (vektor, smart pointer, string, stb.). Egy jó fordító képes elkapni a tartomány a[100] fordítási időben, fogása a p[100] sokkal nehezebb, általában lehetetlen elkapni minden tartomány hiba fordítási időben.

Más példák a nem definiált viselkedést abból ered, hogy a fordítási modell. A fordító nem ismeri fel inkonzisztens meghatározása egy tárgy vagy egy funkció külön-külön összeállított fordítási egységek. Például:

	// file1.c:
	struct S { int x,y; };
	int f(struct S* p) { return p->x; }

	// file2.c:
	struct S { int y,x; }
	int main()
	{
		struct S s;
		s.x = 1;
		int x = f(&s);	// x!=s.x !!
		return 2;
	}

Fordítás fájl1.c es fájl2.c összekapcsolása az eredmények ugyanabba a program illegális mind a C es C++. A linker lehet kapni a következetlen meghatározása S, de nem kötelessége (leginkább nem). Sok esetben ez is elég nehéz elkapni ellentmondások között külön-külön összeállított fordítási egységek. Következetes használata header fájlok segít minimalizálni az ilyen problémákat, illetve vannak olyan jelek, hogy linkers javul. Vegye figyelembe, hogy a C++ linkers elkapja majdnem az összes hibát kapcsolatos következetlenül bejelentett funkciók.

Végül, mi van a látszólag felesleges, inkább idegesítő, nem definiált viselkedést az egyes kifejezések. Például:

	void out1() { cout << 1; }
	void out2() { cout << 2; }

	int main()
	{
		int i = 10;
		int j = ++i + i++;	// value of j unspecified
		f(out1(),out2());	// prints 12 or 21
	}

Az érték a j nem specifikált, hogy lehetővé teszik, fordítók, hogy készítsen optimális kód. Azt állította, hogy a különbség a között, amit elő lehet adni a fordító ezt a szabadságot igénylő “hagyományos bal-jobb értékelést, hogy” jelentős lehet. Nem vagyok róla meggyőződve, de számtalan fordítók “odakint” kihasználva a szabadság, egyesek szenvedélyesen védte, hogy a szabadság, a változás nehéz lenne, s csak évtizedek alatt, hogy behatoljon a távoli sarkokban a C es C++ világ. Csalódott vagyok, hogy nem minden fordító óva kód, például ++i+i++. Hasonlóképpen, a rend értékelése vita nem specifikált.

IMO túl sok “apróság” maradt, nem definiált, nem specifikált, a végrehajtás meghatározott, stb. Azonban, ezt könnyű mondani, de még a példákat, de nehéz megcsinálni. Azt is meg kell jegyezni, hogy nem minden, hogy nehéz elkerülni a legtöbb problémát produkál hordozható kód.


Miért nem lehet meghatározni, korlátait, a sablon paraméterei?

Nos, ez nagyon egyszerű, majd az általános.
Fontolja meg:

        template<class Container>
        void draw_all(Container& c)
        {
                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

Ha van egy típus hiba, akkor a felbontás a meglehetősen bonyolult for_each() hívást. Például, ha az elem típusa, a tartály egy int, aztán valami ismeretlen hiba kapcsolódó, hogy a for_each() hívás (mert nem hivatkozhat a Shape::draw() egy int).

Elkapni az ilyen hibák korai, tudom írni:

        template<class Container>
        void draw_all(Container& c)
        {
                Shape* p = c.front(); // accept only containers of Shape*s

                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

Az inicializálás a hamis változó “p” indít egy érthető hibaüzenet a legtöbb jelenlegi fordítók. Trükkök, mint ez a közös nyelven kell kidolgozni minden új konstrukciók. Gyártási kód, valószínűleg írok valamit, mint:

	template<class Container>
        void draw_all(Container& c)
        {
                typedef typename Container::value_type T;
                Can_copy<T,Shape*>(); // accept containers of only Shape*s

                for_each(c.begin(),c.end(),mem_fun(&Shape::draw));
        }

Ez világossá teszi, hogy ez egy állítás. A Can_copy sablon lehet meghatározni, mint ez:

	template<class T1, class T2> struct Can_copy {
		static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
		Can_copy() { void(*p)(T1,T2) = constraints; }
	};

Can_copy ellenőrzések (fordítási időben), hogy egy T1 lehet rendelni egy T2. Can_copy<T,Shape*> ellenőrzi, hogy T egy Shape* vagy a mutatót egy osztály nyilvánosan származó Shape, vagy írja be a felhasználó által meghatározott átalakítás Shape*. Megjegyzés: ez a meghatározás közel minimális:

  • egy vonal neve a korlátok ellenőrzését, illetve a típusok, amelyek megnézni őket
  • egy sort, hogy felsorolja a konkrét korlátozásokat, ellenőrzött (a constraints() függvény)
  • egy sort kell biztosítanunk, hogy a ravaszt a csekket (kivitelező)

Megjegyzés is, hogy a meghatározás a kívánatos tulajdonságok

  • Lehet kifejezni, korlátok nélkül nyilvánító vagy másolás változók, így az író a kényszer nem kell, hogy a feltételezések arról, hogy milyen típusú inicializálása, hogy tárgyakat lehet másolni, megsemmisült stb. (kivéve persze, ezek a tulajdonságok vizsgálják a megkötés)
  • Nem kód generálódik egy kényszer a jelenlegi fordító
  • Nem makrók van szükség, hogy meghatározzák, vagy használja a megszorítások
  • Jelenlegi fordító ad elfogadható hiba üzenetek egy sikertelen kényszer, beleértve a “megszorítások” (ahhoz, hogy az olvasó egy nyom), a neve a korlátok, valamint a konkrét hiba, ami miatt a hiba (pl. a “nem tudja inicializálni Shape* dupla*”)

Akkor miért van valami, mint Can_copy() – vagy valami még ennél is elegáns – nem a nyelv? D&E-elemzést is tartalmaz a nehézségei kifejező általános megszorítások a C++. Azóta sok ötlet merült fel, hogy ezek a korlátok osztályok könnyebb írni, de még mindig ravaszt jó hibaüzenetek. Például, azt hiszem, hogy a használata a mutató funkció, ahogy én Can_copy származik Alex Stepanov es Jeremy Siek. Nem hiszem, hogy Can_copy() készen for standardization – kell még használni. Emellett különböző formái, korlátai vannak használata a C++ közösségi; nincs még egyetértés abban, hogy pontosan milyen formában korlátok sablonok a legtöbb tényleges széles körű használat.

Azonban az ötlet nagyon általános, sokkal inkább általános, mint nyelvi javasolt, feltéve, hogy kifejezetten a megszorítások ellenőrzése. Elvégre, ha írunk egy sablon van a teljes kifejező ereje C++ áll rendelkezésre. Fontolja meg:

	template<class T, class B> struct Derived_from {
		static void constraints(T* p) { B* pb = p; }
		Derived_from() { void(*p)(T*) = constraints; }
	};

	template<class T1, class T2> struct Can_copy {
		static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
		Can_copy() { void(*p)(T1,T2) = constraints; }
	};

	template<class T1, class T2 = T1> struct Can_compare {
		static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }
		Can_compare() { void(*p)(T1,T2) = constraints; }
	};

	template<class T1, class T2, class T3 = T1> struct Can_multiply {
		static void constraints(T1 a, T2 b, T3 c) { c = a*b; }
		Can_multiply() { void(*p)(T1,T2,T3) = constraints; }
	};

	struct B { };
	struct D : B { };
	struct DD : D { };
	struct X { };

	int main()
	{
		Derived_from<D,B>();
		Derived_from<DD,B>();
		Derived_from<X,B>();
		Derived_from<int,B>();
		Derived_from<X,int>();

		Can_compare<int,float>();
		Can_compare<X,B>();
		Can_multiply<int,float>();
		Can_multiply<int,float,double>();
		Can_multiply<B,X>();
	
		Can_copy<D*,B*>();
		Can_copy<D,B*>();
		Can_copy<int,B*>();
	}

	// the classical "elements must derived from Mybase*" constraint:

	template<class T> class Container : Derived_from<T,Mybase> {
		// ...
	};

Igazából Derived_from nem ellenőrzi levezetése, de az átalakítás, de ez gyakran egy jobb kényszer. Megtalálni a jó nevek korlátok nehéz lehet.


Miért használja a sort(), amikor a “jó öreg qsort()”?

Egy kezdő,

	qsort(array,asize,sizeof(elem),elem_compare);

elég furcsán néz ki, de nehezebb megérteni, mint

	sort(vec.begin(),vec.end());

Egy szakértő, az a tény, hogy sort() általában gyorsabb, mint a qsort() ugyanazokat az elemeket, valamint az azonos összehasonlítás kritériumok gyakran jelentős. Továbbá, a sort() általános, ezért használható bármilyen ésszerű kombinációja konténer típus, elem típusa, valamint összehasonlító kritérium. Például:

	struct Record {
		string name;
		// ...
	};

	struct name_compare {	// compare Records using "name" as the key
		bool operator()(const Record& a, const Record& b) const
			{ return a.name<b.name; }
	};

	void f(vector<Record>& vs)
	{
		sort(vs.begin(), vs.end(), name_compare());
		// ...
	}

Ezen kívül, a legtöbb ember értékelni, hogy valami() típusú széf, hogy nem vet szükséges használni, de ez nem kell, hogy írjon egy összehasonlítása() függvény a standard típusokat.

A részletes magyarázatot lásd a papír”, a Tanulás, a C++ – ban, mint egy Új nyelv”, amely letölthető a kiadványok listáját.

Az elsődleges oka, hogy a sort() általában jobban teljesítenek, mint qsort() ez az összehasonlítás inlines jobb.


Mi is az a függvény objektum?

Egy objektum, amely valamilyen módon viselkedik, mint egy funkció, természetesen. Általában ez azt jelentené, hogy egy tárgy, egy osztály, amely meghatározza az alkalmazás – üzemeltető operator().
Egy függvény objektum egy általánosabb fogalom, mint egy funkció, mert egy függvény objektum lehet államot, hogy továbbra is fennállnak, az egész több hívások (mint egy statikus helyi változó), valamint lehet példa, hogy megvizsgálta kívülről a tárgy (ellentétben a statikus helyi változó). Például:

	class Sum {
		int val;
	public:
		Sum(int i) :val(i) { }
		operator int() const { return val; }		// extract value

		int operator()(int i) { return val+=i; }	// application
	};

	void f(vector<int> v)
	{
		Sum s = 0;	// initial value 0
		s = for_each(v.begin(), v.end(), s);	// gather the sum of all elements
		cout << "the sum is " << s << "\n";
		
		// or even:
		cout << "the sum is " << for_each(v.begin(), v.end(), Sum(0)) << "\n";
	}

Vegye figyelembe, hogy egy függvény objektum egy inline alkalmazás üzemeltető inlines gyönyörűen mert nincsenek mutatók részt, amely megzavarja optimalizáló. Kontraszt: a jelenlegi optimalizáló ritkán (soha?) képes inline egy hívást egy mutató funkció.

Funkció tárgyak széles körben használt, hogy rugalmasságot a standard könyvtár.


Hogyan tudom kezelni memória szivárgás?

Írásban kód, amely nem rendelkezik.

Egyértelműen, ha a kód új műveletek, műveletek törlése, mutató számtani az egész hely, az lesz, hogy elszúrja valahol szivárog, kóbor mutatók, stb. Ez igaz, függetlenül attól, hogy lelkiismeretes vagy, juttatások: végül a komplexitás a kód legyőzi az időt, erőfeszítést, amit megengedhet magának. Ebből következik, hogy a sikeres technikák támaszkodni bujkál elosztása, illetve deallocation belül jobban kezelhető típusok. Jó példa erre a szabványos tartályban. Sikerül memória az elemek jobb, mint aránytalan erőfeszítést. Fontolja meg írom ezt segítség nélkül a húr, a vektor:

	#include<vector>
	#include<string>
	#include<iostream>
	#include<algorithm>
	using namespace std;

	int main()	// small program messing around with strings
	{
		cout << "enter some whitespace-separated words:\n";
		vector<string> v;
		string s;
		while (cin>>s) v.push_back(s);

		sort(v.begin(),v.end());

		string cat;
		typedef vector<string>::const_iterator Iter;
		for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";
		cout << cat << '\n';
	}

Mi lenne az esélye, hogy az első alkalommal? Honnan tudod, hogy nem egy tégla?

Megjegyzés hiányában explicit memória kezelése, makrók, vet, túlfolyó ellenőrzések, kifejezett mérete korlátozza, illetve tanácsot. Segítségével egy függvény objektum, valamint egy szabványos algoritmus, úgy lehet kiküszöbölni, hogy a mutató – mint használni az iterator, de úgy tűnt, túlzás, hogy egy ilyen kis programot.

Ezek a technikák nem tökéletes, nem mindig könnyű használni őket rendszeresen. Ezek azonban alkalmazni meglepően széles körben, majd számának csökkentésével kifejezett összeg, mind deallocations a fennmaradó példák sokkal könnyebb nyomon követni. Olyan korán, mint 1981-ben, rámutattam, hogy azáltal, hogy csökkenti az objektumok száma, hogy volt nyomon követni, hogy a kifejezetten a sok tízezer, hogy egy pár tucat, volt csökken a szellemi erőfeszítést, amellyel a programot, a Herkulesi feladat, hogy valami kezelhető, vagy akár könnyű.

Ha az alkalmazási terület nincs könyvtárak, hogy a programozás, hogy minimalizálja az explicit memória kezelése könnyű, akkor a leggyorsabb módja annak, hogy a program teljes, helyes, lehet, hogy első építeni egy ilyen könyvtár.

Sablonok, valamint a szabványos könyvtárak, hogy ezt használja a konténerek, erőforrás, fogantyúk stb. sokkal könnyebb, mint akár néhány évvel ezelőtt. A használata kivételek teszi, hogy közel lényeges.

Ha nem tudja kezelni elosztási/deallocation hallgatólagosan részeként egy tárgyat kell az alkalmazás mindegy, akkor használja az erőforrás kezelni a legkisebb az esélye egy sárgát. Itt egy példa, ahol vissza kell adnom egy tárgyat juttatott, a szabad tárolni a funkció. Ez egy lehetőség, hogy felejtsd el, hogy törli az objektumot. Végtére is, nem tudjuk megmondani, csak nézi mutató, hogy legyen kötelező, ha olyan, aki ezért felelős. Használata erőforrás kezelni, itt a standard könyvtár auto_ptr, világossá teszi, ahol a felelősség:

 

Gondolj erőforrások általános, ahelyett, hogy egyszerűen csak arról, hogy a memória.

Ha rendszeres alkalmazása ezeket a technikákat nem lehetséges a környezet (van, hogy használja kód máshonnan, része a programot írt a Neander-völgyiek, stb.), ügyeljen arra, hogy a memória szivárgás érzékelő részeként a szabványos fejlesztési eljárás, vagy csatlakoztassa a szemétgyűjtő.


Miért nem tudok folytatás után gyönyörködtető kivétel?

Más szóval, miért nem C++ nyújt egy primitív vissza a pont, ahonnan egy kivétel volt dobva, folyamatos végrehajtás?

Alapvetően valaki folytatása a kivétel kezelő soha nem lehet biztos, hogy a kód után a pontot dobni volt írva, hogy foglalkozik a végrehajtás csak tovább, mint ha mi sem történt volna. Egy kivétel kezelő nem tudom, mennyi az összefüggésben, hogy “azonnal”, mielőtt visszatérnének. Ilyen kód, az író a dobás, az író a fogást kell beható ismerete egymás kódot, majd összefüggésben. Ez létrehoz egy bonyolult kölcsönös függőség, hogy ahol ez megengedett oda vezetett, hogy komoly karbantartási problémák.

Én komolyan venni a lehetőséget, amely lehetővé teszi, folytatása, amikor tervezték a C++ kivételkezelés mechanizmus, valamint megvitatták ezt a kérdést elég részletesen során szabványosítás. Lásd a kivételkezelés fejezet Tervezés mind az Evolúció a C++.

Ha azt szeretné, hogy ellenőrizze, hogy lehet kijavítani a problémát, mielőtt dobna egy kivétel, egy funkciót, amely ellenőrzi, majd a dob, csak akkor, ha a probléma nem helyi. Egy new_handler egy példa erre.


Miért nem C++ egyenértékű, hogy a realloc()?

Ha akarod, akkor természetesen használja a realloc(). Azonban a realloc() csak akkor garantálja, hogy a tömbök által kiosztott malloc() (vagy hasonló funkciók) tartalmazó tárgyak nélkül, a felhasználó által meghatározott másolási konstruktőri. Kérjük, ne feledje, hogy a közhiedelemmel ellentétben naiv várakozások, realloc() néha nem másolja a vita tömb.

C++ ban, egy jobb módja annak, hogy megbirkózzunk átcsoportosítás, hogy használja a standard könyvtár tartály, mint a vector hadd nőjön természetesen.


Miért használja a kivételek?

Milyen jó lehet a kivételek? Az alapvető válasz: a kivételek a hiba kezelése tesz kód egyszerűbb, tisztább, kevésbé valószínű, hogy miss hibák. De mi a baj a “jó öreg errno, ha-mondatok”? Az alapvető válasz: azok, a hiba kezelése, valamint a normál kód szorosan összefonódik. Így a kód zűrös lesz nehéz annak érdekében, hogy foglalkozott a hibák (hiszem, hogy a “spagetti kód”, vagy “a patkányfészek, a vizsgálatok”).

Először vannak dolgok, amiket nem lehet igaz, kivétel nélkül. Fontolja meg egy hibát észlelt egy konstruktor; hogyan jelentés a hiba? Dobsz egy kivétel. Ez az alapja a RAII (Forrás Megszerzése Az Inicializálás), amelyek alapján néhány, a hatékony, modern C++ tervezési technikák: A kivitelező feladata az, hogy meghatározza a invariáns a osztály (létre azt a környezetet, amelyben a tagok funkció, hogy fut), valamint az, hogy gyakran van szükség a beszerzési források, mint például a memória, zárak, fájlok, aljzatok, stb.

Képzeld el, hogy nem volt kivétel, hogy képes lesz megbirkózni a hibát észlelt a kivitelező? Ne feledje, hogy a konstruktőri gyakran hivatkozott inicializálni/konstrukció tárgyak változók:

	vector<double> v(100000);   // needs to allocate memory
	ofstream os("myfile");      // needs to open a file

A vektor vagy ofstream (kimeneti fájl patak) kivitelező, vagy beállíthatjuk, hogy a változó egy “rossz” állapotba (mint ifstream nem alapértelmezés szerint), így minden további művelet meghiúsul. Ez nem ideális. Például abban az esetben, ofstream, a kimeneti egyszerűen eltűnik, ha felejtsd el, hogy ellenőrizze, hogy a nyílt műtét sikerült. A legtöbb osztályok, az eredmény még rosszabb. Legalább, mi volna írni:

	vector<double> v(100000);   // needs to allocate memory
	if (v.bad()) { /* handle error */ }	// vector doesn't actually have a bad(); it relies on exceptions
	ofstream os("myfile");      // needs to open a file
	if (os.bad())  { /* handle error */ }

Ez egy extra teszt / objektum (írni, hogy emlékszik, vagy nem felejtem el). Ez lesz igazán piszkos osztályok tagjai több tárgyat, különösen, ha azok a sub-objektumok egymásra vannak utalva. A további információkat lásd: C++ Programozási Nyelv szakasz 8.3 es Fejezet 14, Függelék E, vagy a (több tudományos) papír Kivétel biztonság: Koncepciók, módszerek.

Szóval írás konstruktőri trükkös lehet, kivétel nélkül, de mi a helyzet a régi, egyszerű függvények? Vagy vissza egy hibakód, vagy állítsa a nem helyi változó (pl. errno). A beállítás egy globális változó nem működik túl jól, kivéve, ha a teszt azonnal (vagy egy másik funkció lehet újra beállítani). Nem is hiszem, hogy ezt a technikát, ha lehet több szál elérése a globális változó. Az a baj a visszatérési értékek kiválasztása a hiba visszatérési érték lehet szükség ész, lehetetlen:

 

Nem lehetséges értéket my_negate() return: Minden lehetséges int a helyes válasz, valami int, nincs helyes válasz, a legtöbb negatív szám, a kettesével-kiegészítő képviselet. Ilyen esetekben van szükség, hogy vissza pár értékek (mint mindig emlékszem, hogy teszt) Lásd a Kezdet programozás könyv további példákat, magyarázatokat.

Gyakori ellenvetés, hogy a használata kivételek:

  • de kivételek drága!: Nem igazán. Modern C++ implementáció csökkenti a rezsi használata kivételek néhány százaléka (3%), valamint az ahhoz képest, hogy nem hiba-kezelés. Írás kód hiba visszatérési kódok, valamint a vizsgálatok nem szabad sem. Általános szabály, kivétel kezelése rendkívül olcsó, ha nem dob kivételt. Nem kerül semmibe egyes implementációk. Az összes költség merülnek fel, ha dob egy kivételt: az, hogy “normális kód” gyorsabb, mint a kód segítségével a hiba visszatérési kódok a vizsgálatokat. Akkor merülnek fel költségek csak akkor, ha van egy hiba.
  • de a JSF++ te magad tilalom alóli kivételek nyíltan!: JSF++ a kemény-valós időben, a biztonság-kritikus alkalmazások (flight control szoftver). Ha egy számítás túl sokáig tart, lehet, hogy valaki meghal. Ezért van, hogy a garancia válasz idő, nem tudjuk – a jelenlegi szinten az eszköz támogatja hogy a kivételek. Ebben az összefüggésben még szabad tárolni elosztási tilos! Valójában, a JSF++ ajánlások a hiba kezelése szimulálni a használata kivételek előre a nap, ahol az eszközök, hogy a dolgok jobbra, azaz a kivételek.
  • de dobott kivétel a kivitelező által hivatkozott új okoz memória szivárgás!: Ostobaság! Ez egy régi történet okozta a bogarat a fordító -, hogy hiba volt, azonnal rögzített több mint egy évtizeddel ezelőtt.

Hogyan tudom használni a kivételek?

Lásd C++ Programozási Nyelv szakasz 8.3 es Fejezet 14 es Függelék E. A függelék összpontosít technikák írás kivételével-a biztonságos kód igényes alkalmazások, s nem írt a kezdők számára is.

C++, kivételek használt jel hibát, hogy nem lehet kezelni, helyben, mint a kudarc, hogy megszerezzék az erőforrás egy konstruktor. Például:

	class Vector {
		int sz;
		int* elem;
		class Range_error { };
	public:
		Vector(int s) : sz(s) { if (sz<0) throw Range_error(); /* ... */ }
		// ...
	};

Ne használjunk kivételeket, mint egyszerűen csak egy másik módja annak, hogy vissza értéket a függvény. A legtöbb felhasználó feltételezni mint a nyelv definíciója arra ösztönzi őket, hogy hogy kivétel-kezelő kód hiba-kezelési kódot, majd megvalósítások vannak optimalizálva, hogy tükrözze azt a feltételezést.

Kulcsfontosságú technika erőforrás beszerzés az inicializálás (néha rövidítve RAII), amely osztályok destructors, hogy rendet az erőforrás menedzsment. Például:

	void fct(string s)
	{
		File_handle f(s,"r");	// File_handle's constructor opens the file called "s"
		// use f
	} // here File_handle's destructor closes the file

Ha a “használja f” része fct() dob egy kivételt, a destructor még hivatkozni a fájlt tökéletesen zárva van. Ez ellentétben áll a közös biztonságos használat:

	void old_fct(const char* s)
	{
		FILE* f = fopen(s,"r");	// open the file named "s"
		// use f
		fclose(f);	// close the file
	}

Ha a “használja f” része old_fct kivételt dob – vagy egyszerűen csak egy vissza – a fájl nem zárt. C programok, longjmp() egy további veszélyt.


Miért nem lehet rendelni egy vector<Apple*>, hogy egy vector<Fruit*>?

Mert ez egy lyukat a típusú rendszer. Például:

	class Apple : public Fruit { void apple_fct(); /* ... */ };
	class Orange : public Fruit { /* ... */ }; // Orange doesn't have apple_fct()

	vector<Apple*> v;	// vector of Apples

	void f(vector<Fruit*>& vf)		// innocent Fruit manipulating function
	{
		vf.push_back(new Orange);	// add orange to vector of fruit
	}

	void h()
	{
		f(v);	// error: cannot pass a vector<Apple*> as a vector<Fruit*>
		for (int i=0; i<v.size(); ++i) v[i]->apple_fct();
	}

Volt a hívás f(v) nem jogi, mi volna, egy Narancs, úgy viselkedik, mint egy Apple.

Alternatív nyelvi tervezési döntés az lett volna, hogy lehetővé teszi a biztonságos átalakítás, de támaszkodni dinamikus ellenőrzés. Az volna szükséges, hogy egy run-time, ellenőrizze az egyes hozzáférést v tagjai, valamint a h() kellett volna, hogy a dob kivételt, amikor találkozik az utolsó eleme v.


Miért nem C++ egy egyetemes osztály Objektum?

  • Nem is kell: a generikus programozás nyújt statikusan típus biztonságos alternatívák a legtöbb esetben. Más esetekben kezelik a több örökséget.
  • Nincs hasznos egyetemes osztály: egy igazán univerzális hordozza, nem a szemantika, a saját.
  • Egy “univerzális” osztály ösztönzi a felületes gondolkodás típusok, interfészek vezet felesleges ellenőrzése.
  • Segítségével egy univerzális base osztály azt jelenti, ára: Tárgyak kell heap-elkülönített, hogy a polimorf; ez azt jelenti, hogy a memória, a hozzáférés költségeit. Halom tárgyak nem természetes módon támogatja a másolás szemantika. Halom tárgyak nem támogatja az egyszerű távcsöves viselkedés, (ami megnehezíti erőforrás-gazdálkodás). Az univerzális base class ösztönzi használjuk a dynamic_cast, illetve más, run-time ellenőrzése.

Igen. Én egyszerűsített érveit; ez egy GYIK, nem egy tudományos dolgozat.


Tényleg kell több örökség?

Nem igazán. Tehetünk anélkül, hogy a többszörös öröklődés segítségével a megoldások, pontosan úgy, ahogy tudunk, anélkül, hogy egyetlen örökség segítségével a megoldások. Tudunk még csinálni anélkül, hogy az osztályok segítségével a megoldások. C a bizonyíték, hogy a viszálykodás. Azonban minden modern nyelv statikus típusú ellenőrzése, valamint öröklési biztosít valamilyen formában a többszörös öröklődés. C++, absztrakt osztályok gyakran szolgálnak interfészek egy osztály lehet sok kapcsolódási pontok. Más nyelvek – gyakran tekinteni “nem MI” – egyszerűen egy külön név, az egyenértékű egy tiszta absztrakt osztály: egy felület. Az ok nyelven nyújt örökség (mind egyes, mind többes), hogy a nyelv által támogatott örökség általában jobb megoldások (pl. használata továbbítása funkciók al-objektumok vagy külön-külön kiosztott objektumok) a könnyű programozás kimutatására, logikai problémák, karbantartási, illetve gyakran a teljesítmény.


Hogyan tudom elolvasni egy string bemenet?

Tudsz olvasni egy egységes, üres megszűnik szó, mint ez:

	#include<iostream>
	#include<string>
	using namespace std;

	int main()
	{
		cout << "Please enter a word:\n";

		string s;
		cin>>s;
	
		cout << "You entered " << s << '\n';
	}

Vegye figyelembe, hogy nincs explicit memória menedzsment nem rögzített méretű puffer, hogy esetleg túlcsordulás.

Ha tényleg szükség van egy egész sort (nem csak egyetlen szó) akkor ezt:

	#include<iostream>
	#include<string>
	using namespace std;

	int main()
	{
		cout << "Please enter a line:\n";

		string s;
		getline(cin,s);
	
		cout << "You entered " << s << '\n';
	}

Egy rövid bevezetés a standard könyvtár létesítmények, mint például a iostream, string, lásd Elefánt 3 TC++PL3 (online elérhető). Részletes összehasonlítása egyszerű felhasználása, C es C++ I/O, lásd a “Tanulás a Szabványos C++ ban, mint egy Új Nyelv”, amely letölthető a saját publikációk listája.


A “generikus” mi sablonok kellett volna?

Nem. generikus elsősorban szintaktikus cukor absztrakt osztályok; ez az, a generikus (függetlenül attól, hogy a Java vagy C# generikus), a program ellen, pontosan meghatározott felületek általában fizetni a költségeit virtuális függvényt hív és/vagy dinamikus vet használható érveket.

Sablonokat támogatja a generikus programozás, sablon függvény, stb. kombinálásával funkciók, mint például egész sablon érvek, specializáció, valamint egységes kezelése beépített, a felhasználó által definiált típusok. Az eredmény az, rugalmasság, általánosság, páratlan teljesítmény a “generikus”. Az STL a legjobb példa.

Egy kevésbé kívánatos eredmény a rugalmasság késő észlelési hibák, eltúlozza rossz hibaüzenetek. Ez jelenleg a címzett közvetve korlátokkal rendelkező osztályok.


Kidobhatom kivétel a kivitelező? A destructor?

Igen: dob kivételt, a kivitelező, amikor nem megfelelően inicializálni (konstrukció) egy tárgy. Nem igazán kielégítő alternatívája a kilépés a kivitelező által dobni.
Nem igazán: a dob kivételt, a pusztító, de ez a kivétel nem hagyja el a pusztító; ha egy destructor kilép egy dobás, mindenféle rossz dolgok történni, mert az alapvető szabályokat a standard könyvtár a nyelv maga lesz megsértette. Nem.
A példák, valamint részletes magyarázatot lásd a Függelék E a C++ Programozási Nyelv.

Van egy ellentmondás: Kivételek, nem használható néhány kemény valós idejű projektek. Lásd például a JSF légi jármű C++ kódolási szabványok.


Miért nem C++ nyújt, hogy “végre” építeni?

Mert a C++ támogatja egy másik, hogy szinte mindig jobb: Az “erőforrás-akvizíció inicializálás” technika (TC++PL3 szakasz 14.4). Az alapötlet az, hogy képviselje egy erőforrás, melyet egy helyi objektum, úgy, hogy a helyi objektum destructor kiadja az erőforrás. Így a programozó nem lehet elfelejteni, hogy kiadja az erőforrás. Például:

	class File_handle {
		FILE* p;
	public:
		File_handle(const char* n, const char* a)
			{ p = fopen(n,a); if (p==0) throw Open_error(errno); }
		File_handle(FILE* pp)
			{ p = pp; if (p==0) throw Open_error(errno); }

		~File_handle() { fclose(p); }

		operator FILE*() { return p; }

		// ...
	};

	void f(const char* fn)
	{
		File_handle f(fn,"rw");	// open fn for reading and writing
		// use file through f
	}

Egy rendszer, szükség van egy “erőforrás kezelni” osztály, minden erőforrás. Azonban nem kell, hogy “végre” záradék az egyes beszerzési forrás. A reális rendszerek, jóval több nyersanyag beszerzése, mint a különböző forrásokat, így az “erőforrás-akvizíció inicializálás” technika vezet, hogy kevesebb kód, mint használni a “végre” konstrukció.

Is, nézd meg a példákat erőforrás menedzsment Függelék  E a C++ Programozási Nyelv.


Mi az az auto_ptr, miért nincs egy auto_array?

Egy auto_ptr egy példa nagyon egyszerű kezelni osztály, meghatározott a <memory> támogató, kivétel biztonsági segítségével az erőforrások megszerzése, az inicializálás technika. Egy auto_ptr rendelkezik mutató, lehet használni, mint egy mutató, illetve törli a tárgy rámutatott, hogy a végén a hatálya alá. Például:

	#include<memory>
	using namespace std;

	struct X {
		int m;
		// ..
	};

	void f()
	{
		auto_ptr<X> p(new X);
		X* q = new X;

		p->m++;		// use p just like a pointer
		q->m++;
		// ...

		delete q;
	}

Ha a kivételt dobott a részét … hogy a tárgy által tartott, p helyesen által törölt auto_ptr – destructor, míg az X által mutatott q kiszivárgott. Lásd TC++PL 14.4.2 a részletekért.

Auto_ptr nagyon könnyű osztály. Különösen ez *nem* hivatkozás számított mutató. Ha a “másolás” egy auto_ptr a másik, a rendelt auto_ptr tartja a mutató, valamint a kijelölt auto_ptr tartja 0. Például:

	#include<memory>
	#include<iostream>
	using namespace std;

	struct X {
		int m;
		// ..
	};

	int main()
	{
		auto_ptr<X> p(new X);
		auto_ptr<X> q(p);
		cout << "p " << p.get() << " q " << q.get() << "\n";
	}

nyomtassa 0 mutató követi, nem-0 mutatót. Például:

	p 0x0 q 0x378d0

auto_ptr::get() függvény a tartott mutató.

Ez a “mozgás szemantika” eltér a szokásos a “másolás szemantika”, lehet meglepő. Különösen, soha ne használjunk auto_ptr tagjaként egy szabványos konténer. A szabványos tárolóedények igényelnek a szokásos másolási szemantika. Például:

	std::vector<auto_ptr<X> >v;	// error

Egy auto_ptr tartja a kurzort egy egyedi elem, nem egy mutató, hogy egy tömb:

	void f(int n)
	{
		auto_ptr<X> p(new X[n]);	// error
		// ...
	}

Ez egy hiba, mert a destructor törli a mutató segítségével a törlés helyett delete[] nem hivatkozhat a destructor az utolsó n-1 Xs.

Szóval kellene használni egy auto_array, hogy tartsa tömbök? Nem. Nincs auto_array. Ennek az az oka, hogy nincs is szükség. A jobb megoldás az, hogy egy vektor:

	void f(int n)
	{
		vector<X> v(n);
		// ...
	}

Kell egy kivétel fordul elő a … rész, v lesz helyesen használták.

C++11 használni Unique_ptr helyett auto_ptr.


Mit ne használja a kivételek?

C++ kivételek célja, hogy támogassa hiba-kezelés. Használja dobja csak jelezni egy hibát kapni, csak adja meg a hiba kezelési műveletek. Vannak más használja a kivételek – a népszerű más nyelveken is – de nem idiomatikus a C++, illetve szándékosan nem támogatott, akkor a C++ implementáció (ezek a megvalósítások vannak optimalizálva azon a feltételezésen alapul, hogy a kivételek használt hibakezelés).

Különösen, dobd nem csak egy alternatív módja a visszatérő érték függvény (hasonló a visszatérés). Így lesz lassan, majd keverd össze a legtöbb C++ programozók használják, mivel kivételek kizárólag a hiba-kezelés. Hasonlóképpen, dobni, nem egy jó módja annak, hogy egy hurok.


Mi a különbség a között, hogy új, malloc()?

malloc() függvény hogy vesz egy szám (byte), mint a vita; visszatér egy void* rámutatva, hogy unitialized tároló. új üzemeltető, hogy vesz egy típusa, valamint a (opcionálisan) egy sor initializers az a típus, mint az érveit; visszatér egy mutatót egy (opcionálisan) inicializálása objektum típusára. A különbség az, hogy a legtöbb nyilvánvaló, ha akar kiosztani egy tárgy, egy felhasználó által definiált típus, nem triviális inicializálás szemantika. Példák:

	class Circle : public Shape {
	public:
		Cicle(Point c, int r);
		// no default constructor
		// ...
	};

	class X {
	public:
		X();	// default constructor
		// ...
	};

	void f(int n)
	{
		void* p1 = malloc(40);	// allocate 40 (uninitialized) bytes

		int* p2 = new int[10];	// allocate 10 uninitialized ints
		int* p3 = new int(10);	// allocate 1 int initialized to 10
		int* p4 = new int();	// allocate 1 int initialized to 0
		int* p4 = new int;	// allocate 1 uninitialized int

		Circle* pc1 = new Circle(Point(0,0),10); // allocate a Circle constructed
						         // with the specified argument
		Circle* pc2 = new Circle;	// error no default constructor

		X* px1 = new X;		// allocate a default constructed X 
		X* px2 = new X();	// allocate a default constructed X 
		X* px2 = new X[10];	// allocate 10 default constructed Xs 
		// ...
	}

Megjegyzés: ha megad egy initializer használata “(érték)” jelölés, inicializálás, hogy érték. Sajnos nem adja meg, hogy egy tömb. Gyakran előfordul, hogy egy vektor egy jobb alternatíva, hogy szabad-store-elkülönített array (pl., úgy kivétel biztonsági).

Amikor használja a malloc() figyelembe kell venni inicializálás, átalakítás a vissza mutató egy megfelelő típusú. Azt is figyelembe veszi, ha a byte-ok száma megfelelő, saját használatra. Nincs teljesítmény különbség a malloc(), illetve az új amikor inicializálás figyelembe.

malloc() jelentések memória kimerültség visszaküldésével 0. új jelentések elosztása, illetve inicializálási hibát dobott kivételek.

Tárgyak által létrehozott új vagy elpusztította a törlés gombra. Területeken a memória által kiosztott malloc() nem kötelező az ingyenes().


Lehet mix C-stílus es C++ stílusú elosztása, illetve üzlet helye?

Igen, abban az értelemben, hogy használhatja a malloc (), illetve az új ugyanabban a programban.

Nem, abban az értelemben, hogy nem lehet kiosztani egy tárgy, a malloc() ingyenes használata, törlése. Nem lehet kiosztani új, törlés ingyenes (a), vagy használja a realloc() a tömb által kiosztott új.

C++ szereplők új, illetve törölni a garancia megfelelő építés, rombolás; hol konstruktőri vagy destructors kell hivatkozni, vannak. C stílusú funkciók malloc(), calloc(), ingyenes (a) realloc() nem biztosítja. Továbbá nincs garancia arra, hogy a mechanizmus által használt új, törlés, hogy megszerezzék, majd engedje el a nyers memória kompatibilis a malloc(), valamint ingyenes(). Ha a keverés stílusok működik a rendszer, akkor egyszerűen egy “szerencsés”.

Ha úgy érzi, szükség van a realloc() – sokan – akkor fontolják meg a standard könyvtár vector. Például

	// read words from input into a vector of strings:

	vector<string> words;
	string s;
	while (cin>>s && s!=".") words.push_back(s);

A vektor kitágul, ha szükséges.

Lásd még a példákat, valamint a vita a “Tanulás a Szabványos C++ – ban, mint egy Új Nyelv”, amely letölthető a kiadványok listáját.


Miért kell egy öntött átalakítani a void*?

C akkor implicit módon átalakítani egy void* egy T*. Ez nem biztonságos. Fontolja meg:

	#include<stdio.h>

	int main()
	{
		char i = 0;
		char j = 0;
		char* p = &i;
		void* q = p;
		int* pp = q;	/* unsafe, legal C, not C++ */

		printf("%d %d\n",i,j);
		*pp = -1;	/* overwrite memory starting at &i */
		printf("%d %d\n",i,j);
	}

A hatások segítségével egy T*, hogy nem pont a T-katasztrofális lehet. Következésképpen a C++, hogy egy T* a void* szüksége van egy kifejezett öntött. Például, hogy a nemkívánatos hatások, mellékhatások a fenti program, meg kell írni:

		int* pp = (int*)q;

vagy, használja az új stílus a kezdéshez, hogy az ellenőrizetlen típus konverziós műveletet jobban látható:

		int* pp = static_cast<int*>(q);

Vet a legjobb elkerülni.

Az egyik leggyakoribb felhasználása, ez nem biztonságos konverzió a C rendelni az eredménye, a malloc(), hogy a megfelelő mutatót. Például:

	int* p = malloc(sizeof(int));

C++ használja a típus biztonságos új szereplő:

	int* p = new int;

Egyébként az új üzemeltető kínál további előnye a malloc():

  • új, nem véletlenül osztja a megfelelő mennyiségű memória,
  • új implicit módon ellenőrzi a memória kimerültség,
  • új rendelkezik inicializálás

Például:

	typedef std::complex<double> cmplx;

	/* C style: */
	cmplx* p = (cmplx*)malloc(sizeof(int));	/* error: wrong size */
							/* forgot to test for p==0 */
	if (*p == 7) { /* ... */ }			/* oops: forgot to initialize *p */

	// C++ style:
	cmplx* q = new cmplx(1,2); // will throw bad_alloc if memory is exhausted
	if (*q == 7) { /* ... */ }

Hogyan tudom meghatározni, hogy egy osztályú állandó?

Ha szeretné, egy állandó, amelyek segítségével az állandó kifejezés, mondjuk, mint egy tömb kötve, két lehetőséged van:

class X {
	static const int c1 = 7;
	enum { c2 = 19 };

	char v1[c1];
	char v2[c2];

	// ...
};

Első pillantásra, a nyilatkozat c1 úgy tűnik, tisztább, de vegye figyelembe, hogy kell használni, hogy az osztály inicializálás szintaxis, a folyamatos kell, hogy legyen egy static const egybeépített vagy felsorolási típus elindítva egy állandó kifejezés. Ez elég korlátozó:

class Y {
	const int c3 = 7;		// error: not static
	static int c4 = 7;		// error: not const
	static const float c5 = 7;	// error: not integral
};

Én inkább a “enum trükk”, mert ez a hordozható, nem csábítanak használata nem szabványos kiterjesztéseket, az az a-osztály inicializálás szintaxis.

Akkor miért ezek a kellemetlen korlátozások léteznek? Egy osztályban általában kijelentette, hogy egy fejléc fájlt, majd egy fejléc fájlt általában tartalmazza a sok fordítási egységek. Ahhoz azonban, hogy elkerüljük a bonyolult linker szabályokat, C++ megköveteli, hogy minden objektum egyedi meghatározás. Ez a szabály lenne, törött, ha C++ engedélyezett-osztály meghatározása szervezetek, ami szükséges, hogy a memóriában tárolt, mint a tárgyak. Lásd D&E magyarázat, hogy a C++ tervezés kompromisszumok.

Több rugalmasság, ha a const nem szükséges használni az állandó kifejezés:

	class Z {
		static char* p;		// initialize in definition
		const int i;		// initialize in constructor
	public:
		Z(int ii) :i(ii) { }
	};

	char* Z::p = "hello, there";

Ön a címét, egy statikus tag, ha (csak akkor) egy osztály meghatározás:

	class AE {
		// ...
	public:
		static const int c6 = 7;
		static const int c7 = 31;
	};

	const int AE::c7;	// definition

	int f()
	{
		const int* p1 = &AE::c6;	// error: c6 not an lvalue
		const int* p2 = &AE::c7;	// ok
		// ...
	}

Miért nem törli nulla, az operandus?

Fontolja meg

	delete p;
	// ...
	delete p;

Ha a rész nem érint o, akkor a második a “delete p;” súlyos hiba, hogy a C++ végrehajtását nem lehet hatékonyan védeni magát (anélkül, hogy szokatlan óvintézkedéseket). Mivel törlése nulla mutató ártalmatlan definíció szerint, egy egyszerű megoldás az lenne, ha a “delete p;” egy “p=0;” miután megtette, amit másra is szükség van. Azonban a C++ nem garancia arra, hogy.

Ennek egyik oka, hogy az operandus a törlés nem kell egy lvalue. Fontolja meg:

	delete p+1;
	delete f(x);

Itt, a végrehajtás törlése nem egy mutatót, amely lehet rendelni nulla. Ezek a példák is lehet, hogy ritka, de ők azt jelenti, hogy nem lehet garantálni, hogy “bármilyen mutatót egy törölt objektum 0.” Egy egyszerűbb lehet kikerülni, hogy a “szabály”, hogy a két mutató, hogy egy tárgy:

	T* p = new T;
	T* q = p;
	delete p;
	delete q;	// ouch!

C++ kifejezetten lehetővé teszi, hogy egy végrehajtását törlés nulla egy lvalue operandus, pedig reméltem, hogy megvalósítások de az ötlet nem úgy tűnik, hogy egyre népszerűbb a előadók.

Ha figyelembe vesszük, nullázás ki mutatók fontos, fontolja meg egy elpusztítani funkció:

	template<class T> inline void destroy(T*& p) { delete p; p = 0; }

Fontolja meg, ez még-még egy ok, hogy minimalizálja kifejezett használni az új, törlés támaszkodva standard könyvtár konténerek, fogantyúk stb.

Vegye figyelembe, hogy halad a mutató hivatkozásként (lehetővé teszi, hogy a mutató nulla volna ki) van az előnye, hogy megakadályozza a destroy() attól, hogy neve egy r érték:

	int* f();
	int* p;
	// ...
	destroy(f());	// error: trying to pass an rvalue by non-const reference
	destroy(p+1);	// error: trying to pass an rvalue by non-const reference

Miért nem a destructor úgynevezett végén hatálya?

Az egyszerű válasz az, hogy “hát persze!”, de nézd már meg, ilyen például, hogy gyakran kíséri ezt a kérdést:

	void f()
	{
		X* p = new X;
		// use p
	}

Ez volt egy (téves) a feltételezés, hogy a tárgy által létrehozott “új” pusztul el, a végén egy funkció.

Alapvetően, csak akkor használja az “új” ha szeretnél egy tárgyat élni, túl az életen át a hatókör létrehozása. Ez kész, akkor kell használni a “törlés” pontot, hogy elpusztítsa azt. Például:

	X* g(int i) { /* ... */ return new X(i); }	// the X outlives the call of g()

	void h(int i)
	{
		X* p = g(i);
		// ...
		delete p;
	}

Ha szeretnél egy tárgyat, hogy él a hatálya csak, ne használd az “új”, hanem egyszerűen definiáljuk:

        {
                ClassName x;
                // use x
        }

A változó hallgatólagosan módon megsemmisült, a végén a hatálya alá.

Kód létrehoz egy objektumot új aztán törli a végén ugyanaz a hatálya ronda, hibára hajlamos, de nem hatékony. Például:

	void fct()	// ugly, error-prone, and inefficient
	{
		X* p = new X;
		// use p
		delete p;
	}

Lehet azt írni, hogy “void main()”?

A meghatározás

	void main() { /* ... */ }

nem, soha nem is volt C++ ez nem is volt C. Lásd az ISO C++ standard 3.6.1[2] vagy az ISO C standard 5.1.2.2.1. Egy megfelelő végrehajtásáról elfogadja

	int main() { /* ... */ }

es

	int main(int argc, char* argv[]) { /* ... */ }

A megfelelő végrehajtás rendelkezhetnek több változata main(), de biztos van mindenkinek visszatérési típus int. A által visszaadott int main() a program visszatérési értéke, hogy “a rendszer”, hogy hivatkozik. A rendszerek nem nyújtanak ilyen létesítmény, a visszatérési érték figyelmen kívül hagyni, de ettől még nem lesz “void main()” jogi C++ vagy jogi C. Még akkor is, ha a fordító elfogadja a “void main()” elkerülése érdekében, vagy a kockázat, hogy úgy, tudatlan, a C, illetve C++ programozók.

C++, a main() nem kell tartalmaznia egy return utasítás. Ebben az esetben a visszaadott érték 0, ami azt jelenti, sikeres végrehajtás. Például:

	#include<iostream>

	int main()
	{
		std::cout << "This program returns the integer value 0\n";
	}

Vegye figyelembe azt is, hogy sem az ISO C++ sem C99 lehetővé teszi, hogy elhagyja a típus egy nyilatkozat. Ez, ellentétben a C89, ARM, C++ ,”int” nem feltételeztem, hogy amennyiben egy típus hiányzik egy nyilatkozat. Következésképpen:

	#include<iostream>

	main() { /* ... */ }

van egy hiba, mert a visszatérési típus main() hiányzik.


Miért nem lehet túlterhelés dot, ::, sizeof, stb.?

A legtöbb szereplők túlterhelt lehet egy programozó. A kivételek

	. (dot)  ::  ?:  sizeof

Nincs alapvető oka, hogy ne engedélyezze a túlterhelt ?:. Én nem látom, hogy be kell vezetni a különleges esetben, ha túlterhelése egy hármas operátor. Megjegyzés: ez a funkció túlterhelése expr1?expr2:expr3 nem lenne képes garantálni, hogy csak az egyik expr2 es expr3 kivégezték.

Méret nem lehet túlterhelni, mert beépített műveletek, például a felszámolás egy mutató egy tömb, hallgatólagosan módon függ. Fontolja meg:

	X a[10];
	X* p = &a[3];
	X* q = &a[3];
	p++;	// p points to a[4]
		// thus the integer value of p must be
		// sizeof(X) larger than the integer value of q

Így, sizeof(X) nem lehet adni egy új, más a jelentése, amelyet a programozó megsértése nélkül az alapvető nyelvi szabályokat.

N::m sem N sem m vagy kifejezések értékei; N, m nevek ismert, hogy a fordító :: végez (fordítási időben) hatálya felbontás helyett egy kifejezés kiértékelési. El lehet képzelni, amely lehetővé teszi, hogy túlterheli az x::y, ahol x az az objektum, mint inkább egy névtér vagy egy osztály, de – ellentétben az első látszat – magában bemutatkozik új szintaxis (lehetővé expr::expr). Nem egyértelmű, mi előnyöket ilyen szövődmény hozza.

Üzemeltető . (dot) elvileg lesz túlterhelt, ugyanazzal a technikával, mint használt ->. Azonban, ha így tesz, ahhoz vezethet, hogy a kérdést, hogy egy művelet célja a tárgy túlterhelését . vagy egy tárgy által említett . Például:

	class Y {
	public:
		void f();
		// ...
	};

	class X {	// assume that you can overload .
		Y* p;
		Y& operator.() { return *p; }
		void f();
		// ...
	};

	void g(X& x)
	{
		x.f();	// X::f or Y::f or error?
	}

Ezt a problémát több szempontból is. Abban az időben a szabványosítás, nem volt nyilvánvaló, hogy melyik lenne a legjobb. További részletekért, lásd D&E.


Meg tudom határozni a saját szereplők?

Sajnálom, hogy nem. Az a lehetőség, hogy figyelembe vették többször is, de minden egyes alkalommal, amikor azt/úgy döntöttünk, hogy a várható problémákat, mint a várható haszon.

Ez nem egy nyelv-műszaki probléma. Még akkor is, amikor először considerd az 1983-ban, tudtam, hogy hogyan lehet végrehajtani. Azonban az a tapasztalatom, hogy amikor túllépünk a triviális példák az emberek úgy tűnik, hogy subtlely különböző vélemények a “nyilvánvaló” értelmében használja az üzemeltető. Egy klasszikus példa a**b**c. Feltételezzük, hogy ** történt, hogy értem exponentiation. Most kell a**b**c jelent (a**b)**c vagy*a*(b*a*c)? Azt hittem, a válasz nyilvánvaló volt a barátaimmal megegyeztünk – aztán rájöttünk, hogy nem értünk egyet, amely állásfoglalás volt a legkézenfekvőbb. A feltételezés az, hogy az ilyen problémák vezetne finom bogarakat.


Hogyan tudom átalakítani egy egész szám, hogy egy húr?

A legegyszerűbb módja az, hogy egy húrok patak:

	#include<iostream>
	#include<string>
	#include<sstream>
	using namespace std;

	string itos(int i)	// convert int to string
	{
		stringstream s;
		s << i;
		return s.str();
	}

	int main()
	{
		int i = 127;
		string ss = itos(i);
		const char* p = ss.c_str();

		cout << ss << " " << p << "\n";
	}

Természetesen ez a technika működik, mely bármilyen típusú hogy kimenetet a <<, hogy egy string. Egy leírás a karakterlánc-folyamok, lásd 21.5.3 a C++ Programozási Nyelv.


Hogyan tudom hívni egy C függvény a C++?

Csak kijelentem, hogy a C függvény “extern “C”” (a C++ kód) úgy hívják (a C vagy C++ kód). Például:

	// C++ code

	extern "C" void f(int);	// one way

	extern "C" {	// another way
		int g(double);
		double h();
	};

	void code(int i, double d)
	{
		f(i);
		int ii = g(d);
		double dd = h();
		// ...
	}

A meghatározások a funkciók néz ki:

	/* C code: */

	void f(int i)
	{
		/* ... */
	}

	int g(double d)
	{
		/* ... */
	}

	double h()
	{
		/* ... */
	}

Vegye figyelembe, hogy a C++ írja be a szabályokat, nem C szabályokat, vagy használt. Tehát nem lehet hívás funkció kijelentette “extern “C”” a téves érvelés. Például:

	// C++ code

	void more_code(int i, double d)
	{
		double dd = h(i,d);	// error: unexpected arguments
		// ...
	}

Hogyan tudom hívni egy C++ funkció a C?

Csak kijelentem, hogy a C++ függvényt, “extern “C”” (alatt C++ kód) úgy hívják (a C vagy C++ kód). Például:

	// C++ code:

	extern "C" void f(int);

	void f(int i)
	{
		// ...
	}

Most f() használható, mint ez:

	/* C code: */

	void f(int);
	
	void cc(int i)
	{
		f(i);
		/* ... */
	}

Természetesen ez csak akkor működik, a nem tag függvények. Ha fel akarod hívni tag funkciók (beleértve virtuális függvények) meg C kell adnia egy egyszerű csomagolást. Például:

	// C++ code:

	class C {
		// ...
		virtual double f(int);
	};

	extern "C" double call_C_f(C* p, int i)	// wrapper function
	{
		return p->f(i);
	}

Most C::f() használható, mint ez:

	/* C code: */

	double call_C_f(struct C* p, int i);
	
	void ccc(struct C* p, int i)
	{
		double d = call_C_f(p,i);
		/* ... */
	}

Ha fel akarod hívni túlterhelt függvények a C, meg kell adnia a csomagolásnak a különböző nevek a C kódot kell használni. Például:

	// C++ code:

	void f(int);
	void f(double);

	extern "C" void f_i(int i) { f(i); }
	extern "C" void f_d(double d) { f(d); }

Most az f() függvényeket lehet használni, mint ez:

	/* C code: */

	void f_i(int);
	void f_d(double);
	
	void cccc(int i,double d)
	{
		f_i(i);
		f_d(d);
		/* ... */
	}

Megjegyzendő, hogy ezek a technikák lehet használni, hogy hívja a C++ könyvtár a C kódot, akkor is, ha nem tud (vagy nem akar) módosítsa, C++ fejlécek.


Az “int* p;” igaz, vagy az “int *p;” ugye?

Mindkettő “jó” abban az értelemben, hogy mindkét érvényes, C, C++ , mindketten pontosan ugyanazt jelenti. Amennyire a nyelvi fogalmak, valamint a fordítók illeti meg lehet mondani, hogy “int*p;” vagy “int * p;”

A választás a “int* p;” es “int *p;” nem arról szól, hogy jól vagy rosszul, de a stílus, valamint a hangsúlyt. C hangsúlyozta kifejezések; nyilatkozatok gyakran tekintik kicsit több, mint egy szükséges rossz. C++, másrészt, van egy nagy hangsúlyt típusok.

Egy ”tipikus C programozó” – írja az “int *p;” magyarázat “*p mi az int” hangsúlyozva, szintaxis, lehet, hogy pont a C ( es C++) nyilatkozat nyelvtan mellett a korrektség, a stílus. Valóban, a * kötődik a nevét o a nyelvtan.

Egy “tipikus C++ programozó” – írja az “int* p;” magyarázat “p mutatót egy int” hangsúlyozva típus. Valóban az a típus, p int*. Nyilvánvaló, hogy én jobban szeretem ezt a hangsúlyt, látni, mint fontos a használata a fejlettebb részein a C++ is.

A kritikus zűrzavar jön (csak) akkor, amikor az emberek megpróbálják kijelentem, több mutató egyetlen nyilatkozat:

	int* p, p1;	// probable error: p1 is not an int*

Helyezze a * közelebb a név nem tesz ez a fajta hiba lényegesen kevésbé valószínű.

	int *p, p1;	// probable error?

Kijelentette, egy név egy nyilatkozat minimalizálja a probléma – különösen, amikor a változók inicializálása. Az emberek sokkal kevésbé valószínű, hogy írni:

	int* p = &i;
	int p1 = p;	// error: int initialized by int*

De ha mégis, a fordító panaszkodni fog.

Ha valamit meg lehet tenni két módja van, hogy valaki zavarodott lesz. Ha valami ízlés kérdése, megbeszélések lehet húzódni. Ragaszkodni egy mutató / nyilatkozat mindig változók inicializálása a zavart eltűnik. Lásd a Tervezési mind az Evolúció a C++ egy hosszabb vita a C nyilatkozat szintaxis.


Melyik elrendezés stílus a legjobb a kód?

Az ilyen stílus a kérdések a személyes ízlés kérdése. Gyakran előfordul, hogy a vélemények arról, hogy a kód elrendezés erősen tartott, de valószínűleg a következetesség sokkal jobban számít, mint egy adott stílusban. Mint a legtöbb ember, azt nehéz felépíteni egy szilárd logikai érv, hogy a preferenciák.

Én személy szerint milyen gyakran nevezik “K&R” stílusban. Ha hozzá egyezmények konstrukciók nem található C lesz, amit néha “Stroustrup” stílusban. Például:

class C : public B {
public:
	// ...
};

void f(int* p, int max)
{
	if (p) {
		// ...
	}

	for (int i = 0; i<max; ++i) {
		// ...
	}
}

Ez a stílus takarékoskodik a függőleges tér jobb, mint a legtöbb elrendezés stílusok, úgy illik, amennyit ésszerű a vászonra. Forgalomba a nyitó zárójel egy függvény egy új sort segít megkülönböztetni a funkció meghatározás a osztály definíciók áttekintése.

Behúzás nagyon fontos.

A tervezési kérdések, mint például a használata absztrakt osztályok a nagyobb felületek, használja a sablonokat, hogy jelen rugalmas típus-biztonságos absztrakciók, valamint megfelelő használatát kivételt jelentenek a hibákat, sokkal fontosabb, mint a választott elrendezés stílus.


Hogyan neve változók? Javasolják, hogy “Magyar”?

Nem, én nem ajánlom, hogy “Magyar”. Tekintettel a “Magyar” (beágyazás egy rövidített változat típusú változó neve) olyan technika, amely hasznos lehet a untyped nyelven, de teljesen alkalmatlan egy nyelv, amely támogatja a generikus programozás objektum-orientált programozás, amely mindkét hangsúlyozzák műveletek kiválasztása alapján írja be az érvek (ismert, hogy a nyelv vagy a futásidejű támogatás). Ebben az esetben a “épület a típusú objektum a nevek” csak bonyolítja, valamint minimálisra csökkenti a absztrakció. Különböző mértékben, hasonló problémák minden rendszer, hogy beágyazza információt nyelv-műszaki adatok (pl. terjedelme, tárolási osztály, szintaktikai kategória) nevek. Egyetértek azzal, hogy bizonyos esetekben az épület típus tippek a változó nevek hasznos lehet, de általában is, de különösen, mint a szoftver fejlődik, ez lesz a karbantartás, figyelmeztető, illetve egy komoly hátrány, hogy jó a kód. Kerülje, mint a pestis.

Szóval, nem mint elnevezése változó, miután a típus; mit szeretek, ajánljuk? Név változó (funkció, típus, bármi) alapján, mi az, vagy nem. Válasszon nevet; ez, válassza a nevet, ami segít az embereknek megérteni a program. Még akkor problémák megértése, hogy mi a program kéne csinálni, ha alom a változók könnyen típusú nevek, mint x1, x2, s3 es p7. Rövidítések, mozaikszavak is megzavarja az embereket, akkor használja takarékosan. Rövidítések kell használni, takarékosan. Fontolja meg, mtbf, TLA, myw, RTFM es NBV. Ezek nyilvánvaló, de várj egy pár hónapja még én is azt elfelejtette, legalább egy.

Rövid név, mint x, vagy értelmes, ha használják hagyományosan; ez, x, kell egy helyi változó vagy egy paraméter kéne egy hurok index.

Ne használjon túl hosszú neveket; ezek a nehéz, hogy írja be, hogy a vonalak olyan hosszú, hogy nem fér el a képernyőn, nehéz gyorsan olvasni. Ezek valószínűleg az ok gombra:

	partial_sum    element_count    staple_partition

Ezek valószínűleg túl hosszú:

	the_number_of_elements    remaining_free_slots_in_symbol_table

Én jobban szeretem hangsúlyozza, hogy külön szót egy azonosítót (e.g, element_count) inkább, mint más anyagok, mint például a elementCount, ElementCount. Soha ne használja a nevek minden betű (pl., BEGIN_TRANSACTION), mert ez hagyományosan fenntartott makrók. Még ha nem is makrókat használni, lehet, hogy valaki tele a header fájlok őket. Egy kezdeti tőke levelet típusok (pl. Tér es Grafikon). C++ nyelvet, majd a standard könyvtár nem használunk nagybetűt, ezért ez int helyett Int, string, inkább, mint. Így képes felismerni a standard típusokat.

Kerülje a nevet, hogy könnyen ködös, félreértettem, vagy megzavarja. Például

	name    names    nameS
	foo     f00
	fl      f1       fI       fi

A karakter 0, o, o, 1, l, különösen hajlamos a bajt.

Gyakran előfordul, hogy a választás a névadás korlátozott a helyi szabályok szerint. Ne feledje, hogy egy fenntartása egységes stílus gyakran fontosabb, mint az, minden kis részlet, ahogy neked a legjobb.


Mondjam “const” előtt, vagy után a típus?

Tettem korábban, de ez ízlés kérdése. “const T” es “T const” voltak – vannak – a (mind) engedélyezett, illetve azzal egyenértékű. Például:

	const int a = 1;	// ok
	int const b = 2;	// also ok

Az a gyanúm, hogy az első verzió megzavarja kevesebb programozók (”inkább a köznyelvi”).

Miért? Amikor feltalálták a “const” (kezdetben nevű “readonly” volt megfelelő “writeonly”), én hagytam, hogy menj, mielőtt vagy miután az a típus, mert olyan egyértelmű. Előre standard C illetve C++ bevezetett néhány (ha van) rendelési szabályok használt leggyakoribb kódok.

Nem emlékszem, hogy bármilyen mély gondolatok vagy benne viták arról, hogy a megrendelés időpontjában. Néhány a korai felhasználók – elsősorban engem – egyszerűen tetszett

	const int c = 10;

jobb mint

	int const c = 10;

abban az időben.

Lehet, hogy befolyásolta az a tény, hogy a legkorábbi példák voltak írva a “readonly” es

	readonly int c = 10;

nem olvastam jobb, mint a

	int readonly c = 10;

A legkorábbi (C vagy C++) a “const” úgy tűnik, hogy hoztak létre (nekem) egy globális helyettesítő “const”, a “readonly”.

Emlékszem, megvitatása szintaxis alternatívák több ember – beleértve. Dennis Ritchie – de én nem emlékszem, melyik nyelven néztem.

Vegye figyelembe, hogy a const mutatók, “const” mindig szó után a “*”. Például:

	int *const p1 = q;	// constant pointer to int variable
	int const* p2 = q;	// pointer to constant int
	const int* p3 = q;	// pointer to constant int

Mire jó static_cast?

Vet általában a legjobb elkerülni. Kivéve a dynamic_cast, a használat magában foglalja annak a lehetőségét, hogy írja be a hiba, vagy a vágásos egy numerikus értéket. Még egy ártatlan kinézetű leadott válhat egy komoly probléma, ha a fejlesztés során vagy karbantartást, az egyik a több fájltípus megnyitásához is benne megváltozott. Például, mit jelent ez?:

	x = (T)y;

Nem tudjuk. Ez attól függ, hogy a típus-T, valamint a típusú x es y. T lehetne a neve, egy osztály, egy typedef, vagy talán egy sablon paraméter. Lehet, hogy x, y vagy skalár változók (T) jelent értéket, átalakítás. Lehet, hogy x egy osztály származó y osztály (T) egy lehangolt. Lehet, hogy x, y vagy független mutató típusok. Mert a C stílusú kezdéshez (T) lehet kifejezni sok logikailag különböző műveletek, a fordító csak a barest esélye, hogy elkapják visszaél. Ugyanezen okból egy programozó lehet, hogy nem tudom pontosan, mi az a kezdéshez. Ez néha minősül előnynek kezdő programozók, a forrása az apró hibákat, amikor a kezdő működne.

Az “új stílusú vet” vezették be kell adni a programozók egy esélyt, hogy állami szándékaikat tisztábban, valamint a fordító fogás több hibát. Például:

	int a = 7;
	double* p1 = (double*) &a;			// ok (but a is not a double)
	double* p2 = static_cast<double*>(&a);	// error
	double* p2 = reinterpret_cast<double*>(&a);	// ok: I really mean it

	const int c = 7;
	int* q1 = &c;			// error
	int* q2 = (int*)&c;		// ok (but *q2=2; is still invalid code and may fail)
	int* q3 = static_cast<int*>(&c);	// error: static_cast doesn't cast away const
	int* q4 = const_cast<int*>(&c);	// I really mean it

Az ötlet az, hogy a konverziók által engedélyezett static_cast valamivel kisebb valószínűséggel vezet hibák, mint azok, amelyek megkövetelik a reinterpret_cast. Elvileg ez lehetséges, hogy az eredmény egy static_cast nélkül casting vissza az eredeti típus, mivel mindig a leadott eredménye reinterpret_cast vissza az eredeti típus használata előtt, hogy biztosítsa a hordozhatóság.

Másodlagos oka bemutatkozik az új stílusú leadott ez volt a C stílusú vet nagyon nehéz, hogy a helyszínen a programot. Például, nem lehet kényelmesen keresése vet segítségével egy egyszerű szerkesztő vagy szövegszerkesztő. Ez a közel-láthatatlanság a C stílusú vet különösen sajnálatos, mert vannak olyan potenciálisan káros. Egy csúnya műtét kellett volna, hogy egy csúnya, szintaktikai forma. Ez a megfigyelés része volt az oka, hogy a választott a szintaxis az új stílusú vet. Egy további oka az volt, hogy az új stílusú vet, hogy megfeleljen a sablon leírásában, hogy a programozók írják a saját vet, különöse  ellenőrzött vet.

Talán, mert static_cast olyan ronda, de így viszonylag nehéz írja be, akkor nagyobb a valószínűsége, hogy kétszer is gondolja meg, mielőtt használja? Az jó lenne, mert vet nagyon többnyire elkerülhető a modern C++.


Szóval, mi a baj a makrók használata?

A makrók nem engedelmeskedik a C++ hatálya alá, majd írja be a szabályokat. Gyakran ez az oka finom, nem túl finom problémák. Következésképpen, C++ nyújt alternatívákat, amelyek jobban illeszkednek a többi C++ mint például a beépített funkciók, sablonok, névterek.

Fontolja meg:

	#include "someheader.h"

	struct S {
		int alpha;
		int beta;
	};

Ha valaki (oktalanul) írt egy makró úgynevezett “alfa” vagy egy makró úgynevezett “béta” ezt nem lehet lefordítani, vagy (rosszabb) összeállítani valami váratlan. Például, “someheader.h” tartalmazhatnak:

	#define alpha 'a'
	#define beta b[2]

Egyezmények, mint például, hogy a makrók (csak makrók) ALLCAPS segít, de nincs nyelv szintű védelmet a makrók. Például, az a tény, hogy a nevek voltak a hatálya alá, a struct nem segít: a Makrók működik a program, mint egy patak, a karakterek, mielőtt a fordító megfelelő látja. Ez egyébként egy fontos oka az, hogy a C illetve C++ program fejlesztői környezet, eszközök volt egyszerű: a humán, mind a fordító különböző dolgokat.

Sajnos, nem feltételezzük, hogy más programozók következetesen kerülni, amit úgy vélik, hogy “hülye”. Például, valaki a közelmúltban arról számolt be, hogy találkozott egy makrót tartalmazó goto. Láttam is, hallottam, érvek, hogy lehet – egy gyenge pillanatban – úgy tűnik, hogy van értelme. Például:

	#define prefix get_ready(); int ret__
	#define Return(i) ret__=i; do_something(); goto exit
	#define suffix exit: cleanup(); return ret__

	int f()
	{
		prefix;
		// ...
		Return(10);
		// ...
		Return(x++);
		//...
		suffix;
	}

Képzeld el, hogy megjelenik, hogy mint fenntartó programozó; “elbújt” a makrók egy fejléc – nem ritka – teszi ezt a fajta “mágikus” nehezebb észrevenni.

Az egyik leggyakoribb finom probléma az, hogy egy függvény stílusú makró nem felel meg a szabályoknak a funkció érv halad. Például:

	#define square(x) (x*x)

	void f(double d, int i)
	{
		square(d);	// fine
		square(i++);	// ouch: means (i++*i++)
		square(d+1);	// ouch: means (d+1*d+1); that is, (d+d+1)
		// ...
	}

A “d+1” a probléma megoldódott hozzáadásával zárójelben a “hívás” vagy a makró definíció:

	#define square(x) ((x)*(x))	/* better */

A probléma azonban a (feltehetően nem szándékolt) dupla értékelése i++ marad.
Igen, azt tudom, hogy vannak dolgok, ismert, mint a makrók, hogy nem is szenved, a problémák, a C/C++ – ban használható makrók. Azonban nem ambíciói javítása C++ makrók. Ehelyett azt javasoljuk, hogy használja a létesítmények a C++ nyelv megfelelő, például a beépített funkciók, sablonok, konstruktőri (inicializálás), destructors (takarítás), kivételek (kilépés kontextusban), stb.


Hogyan kell kiejteni a “cout”?

“cout” hangsúlyos “lásd-out “. A “c” a “character”, mert iostreams térkép értékek a byte (char) ábrázolás.


Hogyan kell kiejteni a “char”?

“char” általában hangsúlyos “tchar”, nem a “kar”. Ez úgy tűnhet, logikátlan, mert a “karakter” ejtik, hogy “ka-rak-ter”, de eddig még senki sem vádolt angol kiejtése (pronunciation),  nem a “pronounciation”, valamint a helyesírási, hogy logikus.

 

Vissza a főoldalra