W moim miejscu pracy ten styl jest szeroko stosowany: -
#include <iostream>
using namespace std;
class A
{
public:
A(int& thing) : m_thing(thing) {}
void printit() { cout << m_thing << endl; }
protected:
const int& m_thing; //usually would be more complex object
};
int main(int argc, char* argv[])
{
int myint = 5;
A myA(myint);
myA.printit();
return 0;
}
Czy jest jakaś nazwa opisująca ten idiom? Zakładam, że ma to zapobiec możliwie dużym obciążeniom związanym z kopiowaniem dużego złożonego obiektu?
Czy jest to ogólnie dobra praktyka? Czy są jakieś pułapki w tym podejściu?
Odpowiedzi:
W UML nazywa się to agregacją. Różni się od kompozycji tym, że obiekt członkowski nie jest własnością klasy odsyłającej. W C ++ możesz zaimplementować agregację na dwa różne sposoby, poprzez odwołania lub wskaźniki.
Nie, to byłby naprawdę zły powód, aby tego używać. Głównym powodem agregacji jest to, że obiekt zawarty nie jest własnością obiektu zawierającego, a zatem ich okresy życia nie są ograniczone. W szczególności okres istnienia obiektu, do którego odwołuje się odwołanie, musi przeżyć ten, który się odwołuje. Mógł zostać utworzony znacznie wcześniej i może żyć dłużej niż do końca życia kontenera. Poza tym stan obiektu, do którego się odwołujemy, nie jest kontrolowany przez klasę, ale może zmieniać się zewnętrznie. Jeśli odniesienie nie jest
const
, klasa może zmienić stan obiektu, który żyje poza nią.To narzędzie do projektowania. W niektórych przypadkach będzie to dobry pomysł, w innych nie. Najczęstszą pułapką jest to, że czas życia obiektu zawierającego odniesienie nigdy nie może przekraczać czasu życia obiektu, do którego się odwołuje. Jeśli otaczający obiekt użyje odwołania po zniszczeniu obiektu, do którego się odwołuje, będziesz mieć niezdefiniowane zachowanie. Ogólnie lepiej jest preferować kompozycję od agregacji, ale jeśli jej potrzebujesz, jest to równie dobre narzędzie, jak każde inne.
źródło
to prevent the possibly large overhead of copying a big complex object?
Nazywa się to iniekcją zależności przez iniekcję konstruktora : klasa
A
pobiera zależność jako argument do swojego konstruktora i zapisuje odwołanie do klasy zależnej jako zmienną prywatną.Na Wikipedii jest ciekawe wprowadzenie .
Dla stałej poprawności napisałbym:
using T = int; class A { public: A(const T &thing) : m_thing(thing) {} // ... private: const T &m_thing; };
ale problem z tą klasą polega na tym, że akceptuje ona odwołania do obiektów tymczasowych:
T t; A a1{t}; // this is ok, but... A a2{T()}; // ... this is BAD.
Lepiej dodać (wymaga przynajmniej C ++ 11):
class A { public: A(const T &thing) : m_thing(thing) {} A(const T &&) = delete; // prevents rvalue binding // ... private: const T &m_thing; };
W każdym razie, jeśli zmienisz konstruktora:class A { public: A(const T *thing) : m_thing(*thing) { assert(thing); } // ... private: const T &m_thing; };
jest prawie pewne, że nie będziesz mieć wskaźnika do tymczasowego .
Ponadto, ponieważ konstruktor pobiera wskaźnik, dla użytkowników jest jasneA
, że muszą zwracać uwagę na czas życia przekazywanego obiektu.Powiązane tematy to:
źródło
Nie ma nazwy dla tego zastosowania, jest po prostu znane jako „Odniesienie jako element członkowski klasy” .
Tak, a także scenariusze, w których chcesz powiązać czas życia jednego obiektu z innym obiektem.
Zależy od sposobu użytkowania. Korzystanie z dowolnej funkcji językowej przypomina „wybieranie koni na kursy” . Należy zauważyć, że istnieją wszystkie ( prawie wszystkie ) funkcje językowe, ponieważ są przydatne w niektórych scenariuszach.
Podczas używania odwołań jako członków klasy należy zwrócić uwagę na kilka ważnych kwestii:
operator=()
i będziesz musiał podać go samodzielnie. Określenie, jakie działanie=
ma podjąć w takim przypadku operator, jest uciążliwe . W zasadzie twoja klasa staje się nieprzenoszalna .NULL
ani nie mogą odnosić się do żadnego innego obiektu. Jeśli potrzebujesz ponownego osadzenia, nie jest to możliwe z odniesieniem, jak w przypadku wskaźnika.Dla większości praktycznych celów (chyba że naprawdę martwisz się wysokim zużyciem pamięci ze względu na rozmiar elementu członkowskiego) wystarczy mieć instancję elementu członkowskiego, zamiast wskaźnika lub elementu referencyjnego. Oszczędza to wiele zmartwień o inne problemy, które przynoszą członkowie referencji / wskaźnika kosztem dodatkowego zużycia pamięci.
Jeśli musisz użyć wskaźnika, upewnij się, że używasz inteligentnego wskaźnika zamiast surowego wskaźnika. To znacznie ułatwiłoby życie dzięki wskazówkom.
źródło
C ++ zapewnia dobry mechanizm zarządzania czasem życia obiektu poprzez konstrukcje klasy / struktury. Jest to jedna z najlepszych cech C ++ w porównaniu z innymi językami.
Gdy zmienne składowe są uwidocznione przez ref lub wskaźnik, zasadniczo narusza to hermetyzację. Idiom ten umożliwia konsumentowi klasy zmianę stanu obiektu A bez posiadania przez niego (A) jakiejkolwiek wiedzy lub kontroli. Umożliwia także konsumentowi utrzymanie odniesienia / wskaźnika do stanu wewnętrznego A poza okresem życia obiektu A. To zły projekt. Zamiast tego klasa mogłaby zostać refaktoryzowana, aby przechowywać ref / wskaźnik do współdzielonego obiektu (nie być jego właścicielem) i można je ustawić za pomocą konstruktora (Mandate the life time rules). Klasa obiektu współdzielonego może być zaprojektowana do obsługi wielowątkowości / współbieżności, w zależności od przypadku.
źródło
Odniesienia do członków są zwykle uważane za złe. Utrudniają życie w porównaniu ze wskazówkami dla członków. Ale nie jest to szczególnie niezwykłe, ani nie jest to jakiś specjalny nazwany idiom lub rzecz. To tylko aliasowanie.
źródło