Jak "zwrócić obiekt" w C++?

Wiem, że tytuł brzmi znajomo, ponieważ jest wiele podobnych pytań, ale proszę o inny aspekt problemu (znam różnicę między posiadaniem rzeczy na stosie a umieszczaniem ich na stosie).

W Javie zawsze mogę zwrócić odniesienia do obiektów "lokalnych"

public Thing calculateThing() {
    Thing thing = new Thing();
    // do calculations and modify thing
    return thing;
}

W C++, aby zrobić coś podobnego mam 2 opcje

(1) Mogę używać referencji, gdy chcę "zwrócić" obiekt

void calculateThing(Thing& thing) {
    // do calculations and modify thing
}

Następnie użyj go jak to

Thing thing;
calculateThing(thing);

(2) lub Mogę zwrócić wskaźnik do dynamicznie przydzielanego obiektu

Thing* calculateThing() {
    Thing* thing(new Thing());
    // do calculations and modify thing
    return thing;
}

Następnie użyj go w ten sposób

Thing* thing = calculateThing();
delete thing;

Przy pierwszym podejściu Nie będę musiał zwalniać pamięci ręcznie, ale dla mnie utrudnia to odczytanie kodu. Problem z drugim podejściem polega na tym, że muszę pamiętać o delete thing;, co nie wygląda zbyt ładnie. Nie chcę zwracać skopiowanej wartości, ponieważ jest nieefektywna( myślę), więc tutaj przychodzą pytania

  • czy jest trzecia rozwiązanie (które nie wymaga kopiowania wartości)?
  • czy jest jakiś problem, jeśli będę trzymać się pierwszego rozwiązania?
  • kiedy i dlaczego powinienem użyć drugiego roztworu?
Author: Shoe, 2010-07-28

8 answers

Nie chcę zwracać skopiowanej wartości, ponieważ jest nieefektywna

Udowodnij to.

Poszukaj RVO i NRVO, a w C++0x move-semantyka. W większości przypadków w C++03 parametr out jest po prostu dobrym sposobem, aby Twój kod był brzydki, a w C++0x faktycznie ranisz się używając parametru out.

Po prostu napisz czysty kod, zwracany przez wartość. Jeśli wydajność jest problemem, profiluj go (przestań zgadywać) i znajdź, co możesz zrobić, aby go naprawić. Prawdopodobnie nie wróci rzeczy z funkcji.


To powiedziawszy, jeśli jesteś martwy na pisanie w ten sposób, prawdopodobnie chcesz zrobić parametr out. Pozwala to uniknąć dynamicznej alokacji pamięci, co jest bezpieczniejsze i na ogół szybsze. Wymaga to, abyś miał jakiś sposób na skonstruowanie obiektu przed wywołaniem funkcji, co nie zawsze ma sens dla wszystkich obiektów.

Jeśli chcesz użyć alokacji dynamicznej, możesz przynajmniej umieścić ją w inteligentnym wskaźniku. (I tak powinno się to robić cały czas) Wtedy nie martw się o kasowanie czegokolwiek, rzeczy są bezpieczne dla WYJĄTKÓW itp. Jedynym problemem jest to, że jest prawdopodobnie wolniejszy niż zwracanie przez wartość i tak!

 111
Author: GManNickG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-28 06:27:20

Po prostu utwórz obiekt i zwróć go

Thing calculateThing() {
    Thing thing;
    // do calculations and modify thing
     return thing;
}

Myślę, że zrobisz sobie przysługę, jeśli zapomnisz o optymalizacji i po prostu napiszesz czytelny kod (będziesz musiał później uruchomić profiler - ale nie Pre-optymalizacji).

 42
Author: Amir Rachum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-29 07:54:27

Po prostu zwróć taki obiekt:

Thing calculateThing() 
{
   Thing thing();
   // do calculations and modify thing
   return thing;
}

Spowoduje to wywołanie konstruktora kopiującego, więc możesz chcieć wykonać własną implementację tego. TAK:

Thing(const Thing& aThing) {}

To może działać trochę wolniej, ale może nie być problemem w ogóle.

Update

Kompilator prawdopodobnie zoptymalizuje wywołanie konstruktora kopiującego, więc nie będzie żadnych dodatkowych kosztów. (Jak zauważył dreamlax w komentarzu).

 16
Author: Martin Ingvar Kofoed Jensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-23 09:57:41

Czy próbowałeś użyć inteligentnych wskaźników (jeśli coś jest naprawdę dużym i ciężkim obiektem), jak auto_ptr:


std::auto_ptr<Thing> calculateThing()
{
  std::auto_ptr<Thing> thing(new Thing);
  // .. some calculations
  return thing;
}


// ...
{
  std::auto_ptr<Thing> thing = calculateThing();
  // working with thing

  // auto_ptr frees thing 
}
 11
Author: demetrios,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-28 06:33:06

Jednym z szybkich sposobów na określenie, czy Konstruktor kopiujący jest wywoływany, jest dodanie logowania do konstruktora kopiującego twojej klasy:

MyClass::MyClass(const MyClass &other)
{
    std::cout << "Copy constructor was called" << std::endl;
}

MyClass someFunction()
{
    MyClass dummy;
    return dummy;
}

Wywołanie someFunction; liczba linii "Copy constructor was called", które otrzymasz, będzie się wahać między 0, 1 i 2. Jeśli nie otrzymasz żadnej, Twój kompilator zoptymalizował wartość zwracaną (co jest dozwolone). Jeśli otrzymasz don ' t get 0, a Twój Konstruktor kopiujący jest śmiesznie drogi, to poszukaj alternatywnych sposobów zwracania instancji z twoje funkcje.

 8
Author: dreamlax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-28 06:35:33

Po pierwsze masz błąd w kodzie, masz na myśli Thing *thing(new Thing()); i tylko return thing;.

  • Użyj shared_ptr<Thing>. Niech to będzie wskazówka. Zostanie ona usunięta, gdy ostatnie odniesienie do Thing wyjdzie poza zakres.
  • pierwsze rozwiązanie jest bardzo powszechne w naiwnych bibliotekach. Ma pewną wydajność i składniowe koszty, unikaj go, jeśli to możliwe
  • użyj drugiego rozwiązania tylko wtedy, gdy możesz zagwarantować, że żadne wyjątki nie zostaną rzucone, lub gdy wydajność jest absolutnie krytyczne (będziesz łączyć się z C lub assembly, zanim to stanie się istotne).
 1
Author: Matt Joiner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-28 06:30:38

Nie chcę zwracać skopiowanej wartości, ponieważ jest nieefektywna

To może nie być prawda. Kompilatory mogą dokonać optymalizacji, aby zapobiec kopiowaniu.

Na przykład GCC dokonuje tej optymalizacji. W poniższym programie nie są wywoływane ani move constructor, ani copy constructor, ponieważ kopiowanie ani przenoszenie nie jest wykonywane. Zwróć również uwagę na adres c. Nawet jeśli obiekt c jest utworzony wewnątrz funkcji f(), c znajduje się w ramce stosu main().

class C {
public:
    int c = 5;
    C() {}
    C(const C& c) { 
        cout << "Copy constructor " << endl;
    }
    C(const C&& c)  noexcept {
        cout << "Move Constructor" << endl;
    }
};

C f() {
    int beforeC;
    C c;
    int afterC;

    cout << &beforeC << endl;   //0x7ffee02f26ac
    cout << &c << endl;         //0x7ffee02f2710 (notice: even though c is instantiated inside f(), c resides in the stack frame of main()
    cout << &afterC << endl;    //0x7ffee02f26a8

    return c;
}

C g() {
    C c = f(); ///neither copy constructor nor move constructor of C are called, since none is done
    cout << &c << endl;  //0x7ffee02f2710
    return c;
}

int main() {
    int beforeC;
    C c = g();    ///neither copy constructor nor move constructor of C are called, since none is done
    int afterC;

    cout << &beforeC << endl; //0x7ffee02f2718 
    cout << &c << endl;       //0x7ffee02f2710 (notice:even though c is returned from f,it resides in the stack frame of main)
    cout << &afterC << endl;  //0x7ffee02f270c
    return 0;
}
 1
Author: Muktadir Rahman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-10 06:18:31

Jestem pewien, że ekspert C++ przyjdzie z lepszą odpowiedzią, ale osobiście podoba mi się drugie podejście. Używanie inteligentnych wskaźników pomaga w problemie zapominania o delete i jak mówisz, wygląda to czystsze niż tworzenie obiektu przed ręką (i nadal konieczność usunięcia go, jeśli chcesz przydzielić go na stercie).

 0
Author: EMP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-28 06:30:01