Kaip skelbiate sąsają C + + sistemoje?

Kaip konfigūruoti sąsają atitinkančią klasę? Ar tai tik abstrakta bazinė klasė?

732
25 нояб. nustatė Aaron Fischer lapkričio 25 d 2008-11-25 19:48 '08, 07:48 pm 2008-11-25 19:48
@ 15 atsakymų

Norėdami išplėsti atsakymą į bradtgmurray , galite padaryti vieną išimtį švaraus virtualaus sąsajos metodo sąrašui, pridėdami virtualų destruktorių. Tai leidžia jums perkelti žymeklio nuosavybę kitai šaliai, nepažeidžiant tam tikros išvestinės klasės. Sunaikintojas neturi nieko daryti, nes sąsaja neturi jokių konkrečių elementų. Funkcijos, tiek virtualios, tiek integruotos, apibrėžimas gali atrodyti prieštaringas, bet manimi tiki, kad taip nėra.

 class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Parent { public: virtual ~Parent(); }; class Child : public Parent, public IDemo { public: virtual void OverrideMe() { //do stuff } }; 

Jums nereikia įtraukti kūno virtualiam destruktoriui - paaiškėja, kad kai kurie kompiliatoriai negali optimizuoti tuščio destruktoriaus, ir jums būtų geriau, jei naudojate numatytąją vertę.

631
25 нояб. Mark Ransom atsakymas lapkričio 25 d 2008-11-25 20:11 '08 at 8:11 pm 2008-11-25 20:11

Sukurkite klasę naudodami virtualius metodus. Naudokite sąsają, kad sukurtumėte kitą klasę, kuri nepaiso šių virtualių metodų.

Grynas virtualus metodas yra klasės metodas, kuris apibrėžiamas kaip virtualus ir priskiriamas 0.

border=0
 class IDemo { public: virtual ~IDemo() {} virtual void OverrideMe() = 0; }; class Child : public IDemo { public: virtual void OverrideMe() { //do stuff } }; 
226
25 нояб. atsakymą pateikė bradtgmurray lapkričio 25 d 2008-11-25 19:53 '08, 19:53, 2008-11-25 19:53

Visa priežastis, dėl kurios turite specialią sąsajos tipo kategoriją be abstrakčių bazinių klasių C # / Java, yra tai, kad C # / Java nepalaiko keleto paveldėjimo,

C ++ palaiko kelis paveldėjimus, todėl nereikia specialaus tipo. Abstrakta bazinė klasė be abstrakčių (grynai virtualių) metodų yra funkciniu požiūriu lygiavertė C # / Java sąsajai.

130
25 нояб. Joel Coehoorn atsakymas lapkričio 25 d 2008-11-25 20:01 '08 at 8:01 pm 2008-11-25 20:01

„C ++“ nėra „sąsajos“ sąvokos. AFAIK, sąsajos pirmą kartą buvo įvestos „Java“, kad apeitų daugkartinio paveldėjimo trūkumą. Ši koncepcija pasirodė esanti labai naudinga, ir tą patį poveikį galima pasiekti C ++ naudojant abstrakčią bazinę klasę.

Abstrakta bazinė klasė yra klasė, kurioje bent viena nario funkcija („Java lingo“ metodas) yra gryna virtuali funkcija, deklaruota naudojant šią sintaksę:

 class A { virtual void foo() = 0; }; 

Abstrakta bazinė klasė negali būti sukurta, t.y. E. Jūs negalite deklaruoti A klasės objekto. Klases galite gauti tik iš A, bet bet kuri gauta klasė, kuri nesuteikia foo() įgyvendinimo, taip pat bus abstrakta. Kad nebebūtų abstrakčios, išvestinė klasė turi pateikti visų grynųjų virtualių funkcijų, kurias ji paveldi, įgyvendinimą.

Atkreipkite dėmesį, kad abstrakta bazinė klasė gali būti didesnė už sąsają, nes ji gali turėti duomenų narius ir nario funkcijas, kurios nėra tik virtualios. Sąsajos ekvivalentas bus abstrakta bazinė klasė be duomenų tik grynosiomis virtualiosiomis funkcijomis.

Ir, kaip pažymėjo Markas Ransomas, abstrakčiai bazinei klasei turėtų būti suteiktas virtualus destruktorius, kaip ir bet kuri bazinė klasė.

44
25 нояб. Atsakymą Dima pateikė lapkričio 25 d. 2008-11-25 20:27 '08, 08:27 pm 2008-11-25 20:27

Kiek galiu patikrinti, labai svarbu pridėti virtualų destruktorių. Naudoju new ir sunaikintus objektus.

Jei sąsajai nepridėsite virtualaus destruktoriaus, tada paveldėto klasės naikintojas nėra vadinamas.

 class IBase { public: virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes virtual void Describe() = 0; // pure virtual method }; class Tester : public IBase { public: Tester(std::string name); virtual ~Tester(); virtual void Describe(); private: std::string privatename; }; Tester::Tester(std::string name) { std::cout << "Tester constructor" << std::endl; this->privatename = name; } Tester::~Tester() { std::cout << "Tester destructor" << std::endl; } void Tester::Describe() { std::cout << "I'm Tester [" << this->privatename << "]" << std::endl; } void descriptor(IBase * obj) { obj->Describe(); } int main(int argc, char** argv) { std::cout << std::endl << "Tester Testing..." << std::endl; Tester * obj1 = new Tester("Declared with Tester"); descriptor(obj1); delete obj1; std::cout << std::endl << "IBase Testing..." << std::endl; IBase * obj2 = new Tester("Declared with IBase"); descriptor(obj2); delete obj2; // this is a bad usage of the object since it is created with "new" but there are no "delete" std::cout << std::endl << "Tester not defined..." << std::endl; descriptor(new Tester("Not defined")); return 0; } 

Jei paleisite ankstesnį kodą be virtual ~IBase() {}; , pamatysite, kad „ Tester::~Tester() destruktorius niekada neskambinamas.

39
05 марта '12 в 20:53 2012-03-05 20:53 atsakymą pateikė Carlos C Soto kovo 05 '12, 20:53 2012-03-05 20:53

Mano atsakymas iš esmės yra toks pat, kaip ir kiti, bet manau, kad yra dar du svarbūs dalykai:

  • Pripažinkite savo sąsajoje virtualų destruktorių arba IDemo jį nuo virtualios, kad būtų išvengta neapibrėžto elgesio, jei kas nors bando ištrinti tokį objektą kaip IDemo .

  • Naudokite virtualų paveldėjimą, kad išvengtumėte daugkartinio paveldėjimo problemų. (Naudojant sąsajas dažnai naudojamas paveldėjimas.)

Ir kaip kiti atsakymai:

  • Sukurkite klasę naudodami virtualius metodus.
  • Naudokite sąsają, kad sukurtumėte kitą klasę, kuri nepaiso šių virtualių metodų.

     class IDemo { public: virtual void OverrideMe() = 0; virtual ~IDemo() {} } 

    arba

     class IDemo { public: virtual void OverrideMe() = 0; protected: ~IDemo() {} } 

    Ir

     class Child : virtual public IDemo { public: virtual void OverrideMe() { //do stuff } } 
31
25 нояб. atsakymas pateiktas Rexxar . 2008-11-25 21:48 '08, 21:48, 2008-11-25 21:48

Visi geri atsakymai aukščiau. Kitas dalykas, kurį reikia turėti omenyje, yra tai, kad jūs taip pat galite turėti švarų virtualų destruktorių. Vienintelis skirtumas yra tas, kad jūs vis dar turite ją įgyvendinti.

Sumišęs

 --- header file ---- class foo { public: foo() {;} virtual ~foo() = 0; virtual bool overrideMe() {return false;} }; ---- source ---- foo::~foo() { } 

Pagrindinė priežastis, dėl kurios norėtumėte tai padaryti, yra suteikti jums sąsajos metodus, tokius kaip mano, bet padaryti juos neprivalomais.

Kad sąsajos klasės klasėje būtų reikalingas grynas virtualus metodas, tačiau visi jūsų virtualūs metodai turi standartinius diegimus, todėl vienintelis būdas, kaip grynasis virtualus žmogus, turėtų būti destruktorius.

Nukentėjusio asmens pakartotinis vykdymas išvestinėje klasėje visai nesvarbu - visuomet iš naujo apibrėžsiu destruktorių, virtualų ar ne, išvestose klasėse.

9
26 нояб. Rodyland atsakymą pateikė lapkričio 26 d. 2008-11-26 01:02 '08 ne 1:02 2008-11-26 01:02

C ++ 11 galite lengvai išvengti paveldėjimo:

 struct Interface { explicit Interface(SomeType other) : foo([=](){ return other.my_foo(); }), bar([=](){ return other.my_bar(); }),  {} explicit Interface(SomeOtherType other) : foo([=](){ return other.some_foo(); }), bar([=](){ return other.some_bar(); }),  {} // you can add more types here... // or use a generic constructor: template<class T> explicit Interface(T other) : foo([=](){ return other.foo(); }), bar([=](){ return other.bar(); }),  {} const std::function<void(std::string)> foo; const std::function<void(std::string)> bar; // ... }; 

Šiuo atveju sąsaja turi atskaitos semantiką, t. Y. turite užtikrinti, kad objektas susiduria su sąsaja (taip pat galite sukurti sąsajas su semantinėmis vertėmis).

Šio tipo sąsajos turi savo privalumus ir trūkumus:

Galiausiai, paveldėjimas yra visų blogių šaknis sudėtingoje programinėje įrangoje. „Sean“ tėvų vertės semantika ir koncepcijos pagrindu sukurtas polimorfizmas (labai rekomenduojama, kad būtų paaiškinti geriausi šio metodo variantai):

Tarkime, turiu programą, kurioje apdoroju savo duomenis polimorfiškai, naudodamas „ MyShape sąsają:

 struct MyShape { virtual void my_draw() = 0; }; struct Circle : MyShape { void my_draw() {  } }; // more shapes: eg triangle 

Programoje „ YourShape sąsaja atlikite tą patį su įvairiomis formomis:

 struct YourShape { virtual void your_draw() = 0; }; struct Square : YourShape { void your_draw() {  } }; /// some more shapes here... 

Dabar pasakykite man, kad norite naudoti kai kurias formas, kurias sukūriau jūsų paraiškoje. Konceptualiai, mūsų formose yra ta pati sąsaja, tačiau norint, kad mano formos dirbtų jūsų programoje, reikia išplėsti savo formas taip:

 struct Circle : MyShape, YourShape { void my_draw() {  }; void your_draw() { my_draw(); } }; 

Pirma, gali būti neįmanoma pakeisti mano skaičiaus. Be to, kelis paveldėjimus lemia kelias į spageti kodą (įsivaizduokite, kad yra trečiasis projektas, kuriame TheirShape sąsaja ... kas atsitiks, jei jie taip pat vadina savo piešimo funkciją my_draw ?).

Atnaujinimas: Yra keletas naujų nuorodų apie polimorfizmą, pagrįstą paveldėjimu:

8
25 июня '13 в 16:51 2013-06-25 16:51 atsakymą gnzlbg pateikė birželio 25 d. 13 val. 4:51 2013-06-25 16:51

Jei naudojate „Microsoft C ++“ kompiliatorių, galite atlikti šiuos veiksmus:

 struct __declspec(novtable) IFoo { virtual void Bar() = 0; }; class Child : public IFoo { public: virtual void Bar() override {  } } 

Man patinka šis metodas, nes jis sukuria žymiai mažesnę kodo sąsają, o sukurtas kodo dydis gali būti daug mažesnis. Naudojant novtable pašalinama visa nuoroda į matomą rodyklę šioje klasėje, todėl niekada negalėsite tiesiogiai ją sukurti. Žr. Dokumentaciją čia - novtable .

7
13 окт. Mark Ingram atsakymas spalio 13 d 2009-10-13 22:53 '09, 10:53 val. 2009-10-13 22:53

Taip pat galite apsvarstyti sutarties klases, įgyvendintas su NVI (virtualios sąsajos šablonas). Pavyzdžiui:

 struct Contract1 : boost::noncopyable { virtual ~Contract1(); void f(Parameters p) { assert(checkFPreconditions(p) pre-condition failure"); // + class invariants. do_f(p); // Check post-conditions + class invariants. } private: virtual void do_f(Parameters p) = 0; }; ... class Concrete : public Contract1, public Contract2 { private: virtual void do_f(Parameters p); // From contract 1. virtual void do_g(Parameters p); // From contract 2. }; 
4
25 нояб. atsakymą pateikė Luc Hermitte lapkričio 25 d. 2008-11-25 20:49 '08, 08:49 PM 2008-11-25 20:49

Nedidelis priedas prie to, kas parašyta ten:

Pirmiausia įsitikinkite, kad jūsų destruktorius taip pat yra grynas virtualus.

Antra, galbūt norėsite paveldėti praktiškai (o ne paprastai), kai ją įgyvendinate, tik geroms priemonėms.

4
25 нояб. Atsakymas pateikiamas Uri 25 lapkričio. 2008-11-25 20:35 '08 at 8:35 pm 2008-11-25 20:35

Aš vis dar esu naujas „C ++“ kūrimas. Pradėjau dirbti su „Visual Studio“ (VS).

Tačiau niekas nemini __interface VS (.NET) . Nesu tikras, kad tai yra geras būdas deklaruoti sąsają. Tačiau atrodo, kad tai suteikia papildomą vykdymą (žr. Dokumentus ). Taigi nereikia aiškiai nurodyti virtual TYPE Method() = 0; kadangi jis bus automatiškai konvertuojamas.

 __interface IMyInterface { HRESULT CommitX(); HRESULT get_X(BSTR* pbstrName); }; 

Tačiau nenaudojau, nes esu susirūpinęs dėl kompleksinio kompiliavimo suderinamumo, nes jis galimas tik .NET.

Jei kas nors turi kažką įdomaus, pasidalinkite.: -)

Ačiū.

1
27 сент. atsakymas suteiktas Yeo 27 sep. 2015-09-27 21:27 '15 , 21:27 2015-09-27 21:27

Tiesa, kad virtual yra de facto standartas apibrėžiant sąsają, nepamirškite apie klasikinį C formos modelį, kuris ateina su dizaineriu C ++:

 struct IButton { void (*click)(); // might be std::function(void()) if you prefer IButton( void (*click_)() ) : click(click_) { } }; // call as: // (button.*click)(); 

Tai turi pranašumą, kad galite vėl susieti įvykių vykdymo laiką, nesukuriant savo klasės dar kartą (nes C ++ neturi polimorfinių tipų keitimo sintaksės, tai yra laikinas sprendimas chameleono klasėms).

Patarimai:

  • Jūs galite paveldėti ją kaip pagrindinę klasę (tiek virtualios, tiek ne virtualios) ir užpildyti savo konstruktoriaus palikuonį.
  • Jūs galite turėti funkcijų rodyklę kaip protected narį ir turėti public ir (arba) getter nuorodą.
  • Kaip minėta pirmiau, tai leidžia jums perjungti įgyvendinimą vykdymo metu. Taigi tai yra būdas valdyti valstybę. Priklausomai nuo skaičiaus, if nurodykite savo kodo pasikeitimus, jis gali būti greitesnis nei switch() es arba if (ruožtu tikimasi, kad jis bus maždaug 3-4, if jis bus, bet jis visada matuojamas pirmiausia.
  • Jei pasirinksite std::function<> per funkcijų rodykles, galite valdyti visus savo objekto duomenis IBase . Nuo šio momento galite turėti IBase vertės schemas (pvz., std::vector<IBase> ). Atkreipkite dėmesį, kad tai gali būti lėčiau, atsižvelgiant į kompiliatorių ir STL kodą; Be to, dabartinės std::function<> diegimo tendencijos paprastai yra viršutinės, palyginti su funkcijų rodikliais ar net virtualiosiomis funkcijomis (tai gali pasikeisti ateityje).
0
26 июля '16 в 18:29 2016-07-26 18:29 atsakymas pateikiamas liepos 26 d., 16 d., 18:29, 2016-07-26 18:29

Čia yra C ++ standarto abstract class apibrėžimas.

n4687

13.4.2

Abstrakta klasė yra klasė, kurią galima naudoti tik kaip kitos klasės pagrindinę klasę; jokie abstrakčios klasės objektai negali būti kuriami, išskyrus tuos klasių, kurie yra iš jo gaunami, sub-objektus. Klasė yra abstrakta, jei ji turi bent vieną gryną virtualią funkciją.

0
07 нояб. atsakymas pateiktas 力 力 07 lapkričio 2017-11-07 04:22 '17 at 4:22 2017-11-07 04:22
 class Shape { public: // pure virtual function providing interface framework. virtual int getArea() = 0; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; class Rectangle: public Shape { public: int getArea() { return (width * height); } }; class Triangle: public Shape { public: int getArea() { return (width * height)/2; } }; int main(void) { Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); cout << "Rectangle area: " << Rect.getArea() << endl; Tri.setWidth(5); Tri.setHeight(7); cout << "Triangle area: " << Tri.getArea() << endl; return 0; } 

Rezultatas: stačiakampio plotas: 35 Trikampio plotas: 17

Mes matėme, kaip abstrakta klasė apibrėžė sąsają getArea (), o kitos dvi klasės įgyvendino tą pačią funkciją, bet su skirtingu algoritmu, skirtu formai būdingos srities skaičiavimui.

-2
19 дек. atsakymas jam duotas 19 d. 2013-12-19 18:45 '13, 18:45 pm 2013-12-19 18:45