» »

"Dinamicna" sprememba razreda v C++

"Dinamicna" sprememba razreda v C++

mojca ::

V C++-u imam razred, ki skrbi za komunikacijo z genericnimi USB in LAN napravami (poveze se na napravo, jo vprasa, kako ji je ime, ... gre za npr. VXI11, ampak to ni relevantno).

Potem pa imam podrazred, ki zna delati s tocno doloceno napravo, vendar ne morem vedeti, ali gre za to napravo, dokler se ne poveze z njo in jo vprasa za ime.

Lahko naredim naslednje (beri kot psevdokodo, ce slucajno kazalci ne delajo):

class Device { ... }
class DeviceX : public Device { ... }
class DeviceY : public Device { ... }

// genericna naprava
Device a(ip);
// kazalec na doloceno napravo
std::unique_ptr<Device> d;

// povezi se na napravo, naredi osnoven handshaking, vprasaj napravo, kako ji je ime
a.connect();
a.getAndProcessIdentity();
if(a.isDeviceX()) {
    // ce gre za dolocen tip naprave, se odklopi in se ponovno priklopi na podrazredu
    a.disconnect();
    d = std::unique_ptr<Device>(new DeviceX(ip));
    d->connect();
} else if(a.isDeviceY()) {
    a.disconnect();
    d = std::unique_ptr<Device>(new DeviceY(ip));
    d->connect();
} else {
    a.disconnect();
    std::cout << "Sorry, don't know how to handle this device.\n";
    exit();
}
d->doSomething();


Ampak tole zgoraj je skrajno grdo. Bi lahko naredila konverzijo na podrazred, ne da bi se morala odklopiti z naprave in nazaj priklopiti nanjo

prtenjam ::

Pozdravljena,

Odgovor na tvoje vprašanje ni preprost in je odvisen on konkretnega izgleda tvojih razredov. Razlog je dokaj preprost. Recimo, da ima tvoj razred Device eno interno spremenljivko (en double) in ko ti ustvariš ta razred, si v spominu rezervirala prostor za en double... Če razred DeviceX doda še en double, potem ne moreš kazalca iz Device preusmeriti v DeviceX, saj bo slednjemu manjkal en double parameter...

Ali drugače rečeno: "Pretvarjanje kazalcev iz sina na očeta je trivialno (vse kar ima sin ima tudi oče, saj si samo nekaj DODA)... Obratno pa seveda ni res očeta ne moreš spremeniti v sina, saj oče nima vseh elementov sina"... Seveda o velja na splošno in je zatorej odvisno od konkretnih razredov...

V tvojem primeru je najlažje tako, da ima že razred DEVICE vse potrebne elemente in virtualne funkcije... vsi ostali razredi pa samo nadgrajujejo (spreminjajo) virtualne funkcije...
Matjaž Prtenjak
https://mnet.si

Zgodovina sprememb…

  • spremenil: prtenjam ()

galu ::

class DeviceX : public Device
{
public:
	DeviceX(Device device) : Device(device) // po domače: ustvari DeviceX na podlagi Device device (njegovi kopiji)
	{
		// dodatna inicializacija
	}
};


to uporabiš v smislu:
if(a.isDeviceX()) {
    d = std::unique_ptr<Device>(new DeviceX(a));
}
Tako to gre.

Zgodovina sprememb…

  • spremenil: galu ()

mojca ::

@galu: hvala za idejo. Copy konstruktorja nocem, ker ne smeta imeti dva razreda povezave na isto napravo (potem lahko dva razreda istocasno piseta po napravi, kar ni OK), ampak z neke vrste move konstruktorjem bi to moralo iti.

galu ::

Quick fix je, da DeviceX deklariraš kot friends class od Device in v Device dodaš nek bool, ki določa, ali je objektu dovoljeno komuniciranje z napravo. In ta bool uporabiš pri vseh operacijah...

vrstico
DeviceX(Device device) : Device(device)

pa spremeniš v
DeviceX(Device &device) : Device(device)

v telesu konstruktorja pa pač spremeniš tisti bool od device objekta na false.

Lahko pa namesto "master bool-a" uporabiš konkretnejšo destrukcijo objekta (in tako "implementiraš" move konstruktor).
Tako to gre.

Zgodovina sprememb…

  • spremenil: galu ()

sebastjan28 ::

Strategy pattern @ Wikipedia se navadno odlično odnese v takih situacijah,....

mojca ::

galu je izjavil:

Quick fix je, da DeviceX deklariraš kot friends class od Device in v Device dodaš nek bool, ki določa, ali je objektu dovoljeno komuniciranje z napravo. In ta bool uporabiš pri vseh operacijah...


Ne, to bi bilo noro. (Kombiniraj še z multithreadingom oz. s tem, da morajo biti spremenljivke thread-safe. Kaj boš pa storil, ko bo kdo po pomoti komuniciral s tem razredom? Vrgel exception? Ostal v neskončni zanki?) Če kopije objektov niso smiselne, je tak pristop recept za polomijo, ker itak z objektom ne moreš delati čisto nič. Pisati itak ne smeš, kakršnekoli vrednosti spremenljivk, ki bi jih morda želel prebrati, pa so itak vse narobe, ker jih je verjetno DeviceX v svoji kopiji že desetkrat vmes spremenil.

(Na začetku sem po pomoti delala kopije pri argumentih v funkciji, namesto da bi uporabljalja kazalce ali reference, potem pa mi seveda ni bilo čisto nič jasno, zakaj stvari ne delajo.)

win64 ::

Morda lahko poizkusiš z "ASP.NET handler" pristopom:
1. Ustvariš seznam "handler-jev" v tvojem primeru možnih naprav. Tej handlerji bodo imeli metodo za preverjanje, če ime pripada napravi in vrnili objekt tipa Device. Vsaka vrsta naprave bo imela svoj handler.
Device* Handle(DeviceContext context){
if(context.DeviceName == "XVII"){
return new DeviceXV(context);
}
return NULL;
}

2. Ustvariš skupen context, kjer imaš vse kar je generičnega. V context-u je lahko implementirano tudi lowlevel branje/pisanje v/iz naprave.
class DeviceContext()
{
 void connect...
 void disconnect...
 void read..
 void write..
 string DeviceName...
}

3. Ustvariš instanco contexta, ustvariš stream za branje in pisanje in prebereš ime naprave.
4. Greš skozi seznam možnih naprav in preveriš katera je ustrezna. Ko en izmed handlerjev pove, da ime pripada napravi le-ta ustvari Device s poslanim context-om.
for /* za vsak handler na seznamu */ {
if((device = deviceHandler.Handle(context)) != NULL){
 break;
}
}

5. delaš z napravo kar hočeš naprej

Dobra stran tega je, da če pride nova vrsta naprave, jo preprosto dodaš na seznam "handlerjev" in je zadeva rešena.

Najbrž je to podobno zgoraj omenjenemu "Strategy pattern", nisem preverjal.

mn ::

Kaj pa nekaj v tej smeri.

Uvedeš Connection objekt, ki se zna povezati in ugotoviti tip naprave.

Ko veš kak tip naprave imaš narediš izpeljan objekt pravega tipa, in mu v konstruktorju daš Connection objekt. Če ti rešitev ustreza napiši pa lahko še o kakih specifikah govoriva.

jernejl ::

Če sem po hitrem branju prav razumel, gre za primer Factory design patterna.
Funkcija doSomething() naj bo v razredu Device označena kot virtual, v podrazredih pa naj bo implementacija.
Za ustvarjanje naprav se naredi DeviceFactory, ki prejme ip, se poveže in ugotovi, za kakšno napravo gre, in naposled vrne objekt ustreznega tipa.
Recimo takole:
http://simplestcodings.blogspot.si/2013...
http://www.bogotobogo.com/DesignPattern...

mojca ::

Hvala. Zdaj sem dobila štiri različne predloge (od tega večina z uporabo starih/klasičnih pointerjev, kar bi si želela popraviti). Si bom prebrala in naštudirala te različne pristope ter razmislila, kaj je najbolj smotrno. Ter vprašala, če mi še kaj ne bo jasno.

PS: Če je mogoče, bi se rada izognila določitvi tipa objekta že takoj pri klicanju konstruktorja. Imam npr. funkcijo, ki naprave poišče po mreži ali na USB-ju in mi vrne std::vector<Device>, pri katerih poznam le tip (USB / ethernet) in naslov. Če bi morala že v konstruktorju za Device določiti vrsto naprave, bi se morala že takoj povezati na vse naprave, da bi sploh lahko skonstruirala listo, kar je spet malenkost neoptimalno in zoprno (na naprave se bom verjetno povezovala v ločenem threadu, ker gre sicer prepočasi). Lahko bi seveda iz funkcije iskanja vrnila vektor z naslovi naprav namesto vektorja z napravami.

Mislim, da bodo zgornji predlogi ok, samo preštudirati jih moram bolj podrobno in se odločiti za najprimernejšega.

Vesoljc ::

loči device detection on dejanske uporabe, neki takega

// pseudo
class BaseDevice {
   virtual int Type();
   virutal void Read();
   virtual void Write();
   // etc...
}

class DeviceX : BaseDevice {
     const int DeviceType = 1337;
}
class DeviceY : BaseDevice {
     const int DeviceType = 21337;
}

class DeviceInfo {
     int Type;
     string Address;
     int Port;
}
class DeviceEnumerator {
     vector<DeviceInfo>  Devices;
     void Enumerate() {}
}

class DeviceFactory {
      BaseDevice CreateDevice(DeviceInfo devinfo) {
         // switch/loop/find correct class type
         return new DeviceX();
      }
}

void main() {
  DeviceEnumerator enumerator;
  enumerator.Enumerate();

  DeviceFactory factory;
  BaseDevice device = factory.CreateDevice(enumerator.Device[0]);
  device.Read();
}
Abnormal behavior of abnormal brain makes me normal...

2f4u ::

mojca je izjavil:

....
Mislim, da bodo zgornji predlogi ok, samo preštudirati jih moram bolj podrobno in se odločiti za najprimernejšega.


Oj;
Mojca, do kakšnega zaključka si prišla?

Spura ::

Madona kake resitve. Vzpostavljanje povezave in ugotavljanje identitete je genericno, torej rabis factory metodo.
Ne znam C++ tko da sintaksa ne bo pravilna.

class Device {
   static std::unique_ptr<Device> connect(String ip) {
    Connection conn = connect();
    Identity identity = parseIdentity(conn);
    if (identity == X) {
      return new unique_ptr(new DeviceX(conn));
    } else if (identity == Y) {
      // itd
    }
  }
}

class DeviceX : Device {
    private Connection conn;
    DeviceX(Connection conn) {
       // DeviceX lepo naprej uporablja obstojec connection
       this.conn = conn;
    }
}

To, da ima Device class metodo getAndProcessIdentity, ki mutira objekt je precejsnja napaka.


Vredno ogleda ...

TemaSporočilaOglediZadnje sporočilo
TemaSporočilaOglediZadnje sporočilo
»

[Android] Thread

Oddelek: Programiranje
81094 (871) Spura
»

[Android] Bluetooth aplikacija

Oddelek: Programiranje
51008 (871) marjan_h
»

[c#] Vprasanje glede eventov

Oddelek: Programiranje
101281 (995) Looooooka
»

Qt Designer -> KDevelop C++ Error

Oddelek: Programiranje
352037 (1567) 'FireSTORM'
»

[c#] enostavna Igrca -> poraba procesorja 95%

Oddelek: Programiranje
192085 (1449) elKaktus

Več podobnih tem