Neočekávaný výstup v c++ třídy a kopírování objektů do jiného objektu

0

Otázka

Já mám robota, který má za ukazatel vektor ints (obchod, práce, historie), nicméně když jsem kopírování objektu z jednoho robota na druhého, a první robot dostane mimo rozsah, a pak jsem vytisknout historii robot to mi dává masivní seznam náhodných čísel. Já jsem se snažil dělat své vlastní kopie konstruktoru a nastavení _history na nové objekty _history hodnotu o hodnotu, ale dává stejnou odpověď.

ROBOT.h

# pragma once
#include <iostream>
#include <vector>

class Robot{
    private:
        int workUnit = 0;
        std::vector<int>* _history; // pointer to vector of ints (NOT a vector of int pointers)

    public:
        Robot() : name("DEFAULT") {_history = new std::vector<int>();};
        Robot(const std::string& name) : name(name){_history = new std::vector<int>();};
        ~Robot(){std::cout << name << ": Goodbye!" << std::endl; delete  _history;};

        std::string whoAmI() const {return name;};
        void setName(const std::string& name){this->name = name;};
        void work();
        void printWork() const;
        std::vector<int>* getHistory() const { return _history; };

    protected:
        std::string name;        
};

ROBOT.cpp

# include "Robot.h"

void Robot::work(){
    workUnit++; 
    _history -> push_back(workUnit);

    std::cout << name << " is working. > " << workUnit <<"\n";
}

void Robot::printWork() const {
    std::cout << "Robot " << name << " has done the following work: ";

    for(const int& record : *_history){
        std::cout << record << " ";
    }

    std::cout << std::endl;
}

HLAVNÍ

#include <iostream>
#include "Robot.h"

int main(){
    Robot r2("Task5 Robo");
    {
        Robot r1("r1");
        r1.whoAmI();
        r1.work();
        r1.work();
        r1.printWork();
        std::cout << "assign r1 to r2..." << std::endl;
        r2 = r1;
        r2.setName("r2");
        r2.whoAmI();
        r2.printWork();
    }
    r2.whoAmI();
    r2.printWork();
    std::cout << "end of example code..." << std::endl;


    return 0;
}


VÝSTUP iam dostat :

r1 is working. > 1
r1 is working. > 2
Robot r1 has done the following work: 1 2
assign r1 to r2...
Robot r2 has done the following work: 1 2
r1: Goodbye!
Robot r2 has done the following work: 7087248 0 6975376 0 0 0 -1124073283 19523 7087248 0 6975408 0 7087248 0 6947152 0 0 -1 -1174404934 19523 7087248 0 6947152 0 1701603654 1917803635 1701602145 1986087516 1634360417 (and lots more random numbers)
c++ class object oop
2021-11-23 18:53:17
3

Nejlepší odpověď

0

Tady je jeden příklad, jak k realizaci pěti speciálních členských funkcí v pravidlo 5. První, výchozí konstruktor a konstruktor, přičemž řetězec by mohl být kombinovány tak, že výchozí konstruktor deleguje na jeden, přičemž řetězec:

Robot(const std::string& name) :
    _history(new std::vector<int>()),
    name(name)
{};

Robot() : Robot("DEFAULT") {}   // Delegate

Zde je pravidlo pěti:

// --- rule of five ---
Robot(const Robot& rhs) :                   // copy constructor
    workUnit(rhs.workUnit),
    _history(new std::vector<int>(*rhs._history)),
    name(rhs.name)
{}
Robot(Robot& rhs) noexcept :                // move constructor
    workUnit(rhs.workUnit),
    // use exchange to steal pointer and replace with nullptr:
    _history(std::exchange(rhs._history, nullptr)),
    name(std::move(rhs.name))
{}
Robot& operator=(const Robot& rhs) {        // copy assignment operator
    workUnit = rhs.workUnit;
    *_history = *rhs._history; // use vector's copy assignment operator
    name = rhs.name;
    return *this;
}
Robot& operator=(Robot&& rhs) noexcept {    // move assignment operator
    workUnit = rhs.workUnit;
    // swap pointers, let rhs destroy *this old pointer:
    std::swap(_history, rhs._history);
    name = std::move(rhs.name);
    return *this;
}
~Robot() {                                  // destructor
    std::cout << name << ": Goodbye!\n";
    delete _history;
};
// --- rule of five end ---

Když máte co do činění s raw vlastnit ukazatele, můžete použít std::unique_ptr se dostat některé zdarma. Místo

std::vector<int>* _history;

to:

std::unique_ptr<std::vector<int>> _history;

Takže třída se stává:

Robot(const std::string& name) :
    _history(std::make_unique<std::vector<int>>()),
    name(name)
{};
Robot() : Robot("DEFAULT") {}   // Delegate

A pravidlo pěti stává trochu jednodušší:

// --- rule of five ---
Robot(const Robot& rhs) :                         // copy constructor
    workUnit(rhs.workUnit),
    _history(std::make_unique<std::vector<int>>(*rhs._history)),
    name(rhs.name)
{}

// move constructor handled by unique_ptr, just default it:
Robot(Robot& rhs) noexcept = default;

Robot& operator=(const Robot& rhs) {              // copy assignment operator
    workUnit = rhs.workUnit;
    *_history = *rhs._history;
    name = rhs.name;
    return *this;
}

// move assignment operator handled by unique_ptr, just default it:
Robot& operator=(Robot&& rhs) noexcept = default;

~Robot() {                                        // destructor
    std::cout << name << ": Goodbye!\n";
    // delete _history;                      // no delete needed, unique_ptr does it
};
// --- rule of five end ---

Můžete také chtít vrátit _history odkazem z getHistory(). Níže pracuje pro obě raw ukazatel a unique_ptr verze a dává hezčí rozhraní do třídy:

const std::vector<int>& getHistory() const { return *_history; };
      std::vector<int>& getHistory()       { return *_history; };
2021-11-23 20:25:25
0

Když zničíte robota, zničit jeho pracovní historii. Co se stane při kopírování robot, je, že dostane kopii ukazatel na pracovní historii. Jinými slovy, druhý robot má ukazatel na přesně stejný vektor celých čísel, že první robot vytvořil.

Nyní, když první robot je zničen, to odstraní historie, které je vlastní. To je důvod, proč druhý robot je pracovní historie je neplatný, když vytištěno: že paměť byla uvolněna.

Lze navrhnout dvě možné řešení pro toto. Jeden je zavést "Pravidlo 5", který, mimo jiné, umožňují určit (definovat copy konstruktor a operátor přiřazení), jak jeden robot mohl udělat kopii jiného robota, včetně vytvoření pracovní historii, že by vlastní a které nemohly být odstraněny tím, že první robot. Druhá je použití "Shared ukazatel" řídit životnost pracovní historii.

Vzhledem k tomu, že práce historie zní jako něco, co by nemělo být sdílen více robotů, šel bych pro první možnost.

2021-11-23 20:20:01

To dává smysl, děkuji, snažil jsem se definovat copy konstruktor a přidání hodnot k _history jeden po druhém, ale stále stejná chyba, mohl byste poskytnout základní provedení? děkuji.
barrybeers1998

Omlouvám se, nepodíval jsem se pořádně na svůj kód. Používáte operátor přiřazení kopírování robot, ne kopie konstruktoru. Pravidlo 3 a pravidlo 5 říká, že pokud jste definující jeden z nich, měli byste definovat všechny z nich, ujistěte se, že máte všechny základy pokryty. Skutečnost, že definovat copy konstruktor nevyřešil váš problém je příklad, proč jsou ta pravidla tak užitečné
Tim Randall
0
r2 = r1;

Používá implicitně prohlásil splácet kopírování operátor přiřazení. Vzhledem k tomu, výchozí provádění jednoduše dělá memberwise kopírování, starý _history ukazatel je jednoduše přepsán a vektor je nečekaně ve vlastnictví 2 objekty kromě staré vektor uložen v r2 před úkol není osvobozen správně.

Ty by měly realizovat přesunout konstruktor + přesunout přiřazení operátora, copy constructor + kopie přiřazení operátora, nebo obě dvojice.

To je jen nutné to udělat i když, pokud budete mít _history vektorové syrové ukazatel; mění se std::vector<int> výchozí implementace konstruktérů/operátory přiřazení jsou přítomny a pracujeme, mění na std::unique_ptr<std::vector<int>> výsledkem by v kopírovací operátor přiřazení/copy constructor být odstraněn.

Poznámka: Pro všechny přístupy by jsi měl změnit návrat typů whoAmI a getHistory jak je popsáno v poslední možnost.

Operátor přiřazení kopírování

To udržuje oba objekty, "neporušený", po zadání.

Vlastní implementaci správně zkopírovat ukazatel je nutné.

class Robot{
    ...

public:
    ...

    Robot(Robot const& other)
        : workUnit(other.workUnit), _history(new std::vector<int>(*other._history)), name(other.name)
    {}

    Robot& operator=(Robot const& other)
    {
        workUnit = other.workUnit;
        *_history = *other._history;
        name = other.name;
        return *this;
    }
    ...
};

Přesunout úkol

To ponechává vyžaduje, abyste změnit přiřazení r2 = std::move(r1); na odchodu r1 ve státě, kde pouze destruktor je zaručena do práce. Všimněte si, že to bys neměla tisk name v destructor, od r2jméno bylo přesunuto od.

class Robot{
    ...

public:
    ...

    Robot(Robot && other) noexcept
        : workUnit(other.workUnit), _history(other._history), name(std::move(other.name))
    {
        other._history = nullptr; // prevent double free; we're the sole owner of the vector now
    }

    Robot& operator=(Robot && other) noexcept
    {
        workUnit = other.workUnit;

        delete _history; // old history no longer needed -> free to avoid memory leak
        _history = other->_history;
        other._history = nullptr; // prevent double free; we're the sole owner of the vector now
        
        name = std::move(other.name);
        return *this;
    }
    ...
};

Jednoduchá možnost (Doporučeno)

Jít s std::vector<int> a jít s výchozí konstruktory:

class Robot{

private:
    int workUnit = 0;
    std::vector<int> _history; // pointer to vector of ints (NOT a vector of int pointers)

public:
    Robot() : name("DEFAULT") {}
    Robot(const std::string& name) : name(name){}
    ~Robot(){std::cout << name << ": Goodbye!" << std::endl; }

    Robot(Robot const&) = default;
    Robot& operator=(Robot const&) = default;

    // don't print name in the destructor, if you uncomment the following 2 members
    // Robot(Robot&&) = default;
    // Robot& operator=(Robot&&) = default;

    std::string const& whoAmI() const {return name;} // user should be able to decide, if a copy is needed
    void setName(const std::string& name){this->name = name;}
    void work();
    void printWork() const;
    std::vector<int> const& getHistory() const { return _history; } // don't return raw a pointer here

    std::vector<int>& getHistory() { return _history; } // overload only needed, if the history needs to be modifiable from the outside

protected:
    std::string name;  

      
};

Pokud historie se vrátil musí být modifikovatelné pro const objekt, zvážit _history mutable.

2021-11-23 19:24:18

V jiných jazycích

Tato stránka je v jiných jazycích

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................