2010-03-23 11 views
40

Próbuję się nauczyć języka C++ i próbuję zrozumieć zwracane obiekty. Wydaje mi się, że widzę 2 sposoby na zrobienie tego i muszę zrozumieć, co jest najlepszą praktyką.Najlepsza praktyka C++: Powracanie do obiektu referencyjnego

Opcja 1:

QList<Weight *> ret; 
Weight *weight = new Weight(cname, "Weight"); 
ret.append(weight); 
ret.append(c); 
return &ret; 

Opcja 2:

QList<Weight *> *ret = new QList(); 
Weight *weight = new Weight(cname, "Weight"); 
ret->append(weight); 
ret->append(c); 
return ret; 

(oczywiście, nie może zrozumieć to jednak zarówno).

Który sposób jest uważany za najlepszą praktykę i powinien być przestrzegany?

+1

Oba warianty wyglądają okropnie źle (chociaż tylko pierwszy jest w rzeczywistości uszkodzony). –

+0

Jeśli są to 'QList' z biblioteki' Qt', to mają specjalne właściwości liczenia akcji, które nie są typowe dla biblioteki standardowej lub innych typów regularnych. – woolstar

Odpowiedz

46

Opcja 1 jest wadliwa. Po zadeklarowaniu obiektu tylko żyje w zasięgu lokalnym. Zniszcza się, gdy funkcja się kończy. Można jednak dokonać tej pracy z

return ret; // no "&" 

Teraz, chociaż ret jest zniszczona, kopia wykonana jest pierwszy i przekazywane z powrotem do osoby dzwoniącej.

Jest to ogólnie preferowana metodologia. W rzeczywistości, operacja kopiowania i niszczenia (która nic nie robi) jest zwykle elided, or optimized out i otrzymujesz szybki, elegancki program.

Opcja 2 działa, ale masz wskaźnik do sterty. Jednym ze sposobów patrzenia na C++ jest to, że celem tego języka jest uniknięcie ręcznego zarządzania pamięcią. Czasami użytkownik chce zarządzać obiektami na stercie, ale nadal pozwala opcja 1, że:

QList<Weight *> *myList = new QList<Weight *>(getWeights()); 

gdzie getWeights jest Twój przykład funkcja. (W takim przypadku konieczne może być zdefiniowanie konstruktora kopiowania QList::QList(QList const &), ale podobnie jak w poprzednim przykładzie, prawdopodobnie nie zostanie wywołany.)

Należy również unikać listy wskaźników. Lista powinna przechowywać obiekty bezpośrednio. Spróbuj użyć std::list ... ćwiczenie z funkcjami językowymi jest ważniejsze niż ćwiczenie implementacji struktur danych.

6

Użyj opcji # 1 z niewielką zmianą; zamiast zwracania odwołania do obiektu utworzonego lokalnie, zwróć jego kopię.

tj return ret;

Większość kompilatory C++ wykonać Return value optimization (RVO) celu optymalizacji dala tymczasowy obiekt utworzony do przechowywania wartości zwracanej przez funkcję jest.

+1

Potrafię potwierdzić to zachowanie w g ++ bez żadnych flag - optymalizuje on instrukcję return i po prostu zwraca adres do obiektu zamiast go kopiować (ogólnie). –

+0

Nawet bez RVO 'QList' robi wewnętrznie kopię na piśmie, więc kopia z tymczasowego jest mniej lub bardziej wolna. –

5

Ogólnie nie powinieneś zwracać referencji ani wskaźnika. Zamiast tego zwróć kopię obiektu lub zwróć klasę inteligentnego wskaźnika, który jest właścicielem obiektu. Ogólnie należy używać alokacji pamięci statycznej, chyba że rozmiar zmienia się w czasie wykonywania lub okres istnienia obiektu wymaga jego alokacji przy użyciu alokacji dynamicznej pamięci masowej.

Jak już wspomniano, przykład zwracania przez referencję zwraca odniesienie do obiektu, który już nie istnieje (ponieważ wykracza poza zakres), a zatem wywołuje niezdefiniowane zachowanie.To jest powód, dla którego nigdy nie powinieneś zwracać referencji. Nigdy nie powinieneś zwracać surowego wskaźnika, ponieważ własność jest niejasna.

Należy również zauważyć, że zwracanie przez wartość jest niezwykle tanie ze względu na optymalizację wartości zwrotu (RVO), a wkrótce będzie jeszcze tańsze ze względu na wprowadzenie odniesień rvalue.

+0

A nawet bez RVO lub przenoszenia semantyki 'QList' robi wewnętrznie kopię na piśmie, więc kopia z tymczasowego jest mniej lub bardziej wolna. –

1

przekazywanie & powracające referencje zachęca do odpowiedzialności.! musisz zachować ostrożność, że kiedy modyfikujesz niektóre wartości, nie ma żadnych efektów ubocznych. tak samo w przypadku wskaźników. Polecam ci retunowanie przedmiotów. (BUT IT VERY-MUCH DEPENDS ON WHAT EXACTLY YOU WANT TO DO)

W opcji 1 zwracasz adres i jest to BARDZO złe, ponieważ może to prowadzić do niezdefiniowanego zachowania. (Ret będą zwalniane, ale adres y'll dostępowego RET w wywołanej funkcji)

więc używać return ret;

1

To generalnie złą praktyką przydzielić pamięci, który ma zostać uwolniony gdzie indziej. To jeden z powodów, dla których mamy C++, a nie tylko C. (Ale doświadczeni programiści pisali obiektowo zorientowany kod w C na długo przed Age of Stroustrup.) Dobrze skonstruowane obiekty mają szybkie operatory kopiowania i przypisania (czasami używają liczenia referencji) , i automatycznie zwalniają pamięć, którą "posiadają", kiedy zostaną uwolnieni, a ich DTOR zostanie wywołany automatycznie. Możesz więc rzucać nimi radośnie, zamiast używać do nich wskaźników.

Dlatego w zależności od tego, co chcesz zrobić, najlepsza praktyka jest bardzo prawdopodobna "żadna z powyższych". Ilekroć masz ochotę użyć "nowego" nigdzie indziej niż w CTOR, pomyśl o tym. Prawdopodobnie nie chcesz w ogóle używać "nowego". Jeśli tak, wynikowy wskaźnik prawdopodobnie powinien być zawinięty w jakiś inteligentny wskaźnik. Możesz iść na tygodnie i miesiące bez wywoływania "nowych", ponieważ "nowe" i "usuń" są obsługiwane w standardowych klasach lub szablonach klas, takich jak std :: list i std :: vector.

Jednym wyjątkiem jest używanie starej biblioteki modowej, takiej jak OpenCV, która czasami wymaga utworzenia nowego obiektu i przekazania wskaźnika do systemu, który bierze na siebie odpowiedzialność.

Jeśli qlist i waga są poprawnie napisane aby posprzątać po sobie w swoich DTORS, co chcesz to,

QList<Weight> ret(); 
Weight weight(cname, "Weight"); 
ret.append(weight); 
ret.append(c); 
return ret; 
1

Jak już wspomniano, to lepiej unikać przydzielania pamięci, które muszą być zwalniane gdzie indziej. To właśnie Wolę robić (... te dni):

void someFunc(QList<Weight *>& list){ 
    // ... other code 
    Weight *weight = new Weight(cname, "Weight"); 
    list.append(weight); 
    list.append(c); 
} 

// ... later ... 

QList<Weight *> list; 
someFunc(list) 

Nawet lepiej - całkowicie uniknąć new i korzystania std::vector:

void someFunc(std::vector<Weight>& list){ 
    // ... other code 
    Weight weight(cname, "Weight"); 
    list.push_back(weight); 
    list.push_back(c); 
} 

// ... later ... 

std::vector<Weight> list; 
someFunc(list); 

Zawsze można użyć bool lub enum jeśli chcesz aby zwrócić flagę statusu.

+0

Pierwszy z tych fragmentów kodu prawdopodobnie jest błędny. Jeśli QList jest poprawnie napisany, to fragment kodu ma wyciek pamięci.Jeśli DTOR dla QList jest zapisany, aby usunąć obiekty Weight-to wskazuje (nie najlepsza praktyka), wtedy pierwszy snippet jest poprawny. –

+0

@Jive, nigdy nie dotknąłem QList przez całe życie. Pokazałem tylko, że wolę przydzielać pojemniki na stosie i przekazywać je jako argumenty, niż zwracać mi wskaźnik. –

-3

Wszystkie te odpowiedzi są poprawne, unikaj Wskaźników, korzystaj z konstruktorów kopiujących itp. Jeśli nie musisz tworzyć programu wymagającego dobrej wydajności, z mojego doświadczenia wynika, że ​​większość problemów związanych z wydajnością dotyczy konstruktorów kopiowania i kosztów ogólnych. spowodowane przez nich. (A inteligentne wskaźniki nie są lepsze na tym polu, chciałbym usunąć cały mój kod doładowania i wykonać ręczne usuwanie, ponieważ wykonanie tej pracy zajęło zbyt wiele milisekund).

Jeśli tworzysz "prosty" program (chociaż "prosty" oznacza, że ​​powinieneś używać java lub C#), użyj konstruktorów kopiowania, unikaj wskaźników i używaj inteligentnych wskaźników, aby zwolnić używaną pamięć, jeśli tworzysz Złożone programy lub potrzebujesz dobrej wydajności, użyj wskaźników w każdym miejscu i unikaj konstruktorów kopiowania (jeśli to możliwe), po prostu stwórz zestaw reguł, aby usunąć wskaźniki i użyj valgrind do wykrycia wycieków pamięci, Może będę uzyskać pewne punkty ujemne, ale myślę, że będziesz musiał uzyskać pełny obraz, aby wybrać swój projekt.

Myślę, że powiedzenie „jeśli jesteś powrocie wskazówek projekt jest źle” jest trochę mylące. Parametry wyjściowe wydają się być mylące, ponieważ nie jest to naturalny wybór dla wyników "powracających".

Wiem, że to pytanie jest stary, ale nie widzę jakikolwiek inny argument, wskazując, narzut wykonania tego wyboru projektu.

1

Na podstawie doświadczenia nie należy używać prostych wskaźników, ponieważ można łatwo zapomnieć o dodaniu odpowiednich mechanizmów niszczenia.

Jeśli chcesz uniknąć kopiowania, można przejść do realizacji Waga klasy z konstruktorem kopiującym i skopiuj operator wyłączył:

class Weight { 
protected: 
    std::string name; 
    std::string desc; 
public: 
    Weight (std::string n, std::string d) 
     : name(n), desc(d) { 
     std::cout << "W c-tor\n"; 
    } 
    ~Weight (void) { 
     std::cout << "W d-tor\n"; 
    } 

    // disable them to prevent copying 
    // and generate error when compiling 
    Weight(const Weight&); 
    void operator=(const Weight&); 
}; 

Następnie dla klasy wykonawczego pojemnik, należy shared_ptr lub unique_ptr do wdrożenie elementu danych:

template <typename T> 
class QList { 
protected: 
    std::vector<std::shared_ptr<T>> v; 
public: 
    QList (void) { 
     std::cout << "Q c-tor\n"; 
    } 
    ~QList (void) { 
     std::cout << "Q d-tor\n"; 
    } 

    // disable them to prevent copying 
    QList(const QList&); 
    void operator=(const QList&); 

    void append(T& t) { 
     v.push_back(std::shared_ptr<T>(&t)); 
    } 
}; 

Twoja funkcja dodawania elementu byłoby skorzystać lub Return Value Optimization i nie wywołać konstruktor kopiujący (co nie jest określony):

QList<Weight> create (void) { 
    QList<Weight> ret; 
    Weight& weight = *(new Weight("cname", "Weight")); 
    ret.append(weight); 
    return ret; 
} 

Po dodaniu elementu, niech pojemnik wziąć własność obiektu, więc nie zwalnianie go:

QList<Weight> ql = create(); 
ql.append(*(new Weight("aname", "Height"))); 

// this generates segmentation fault because 
// the object would be deallocated twice 
Weight w("aname", "Height"); 
ql.append(w); 

Albo lepiej, zmusić użytkownika do przekazania implementacji qlist tylko inteligentne wskaźniki:

void append(std::shared_ptr<T> t) { 
    v.push_back(t); 
} 

A poza klasa qlist będziesz go używać jak:

Weight * pw = new Weight("aname", "Height"); 
ql.append(std::shared_ptr<Weight>(pw)); 

Korzystanie shared_ptr również mogłaby „wziąć” obiektów z kolekcji, należy wykonać kopie, usunięcia ze zbioru, ale używać lokalnie - za kulisami to być tylko same tylko przedmiot.

Powiązane problemy