c++ 类 中的意外输出并将对象复制到另一个对象

Unexpected output in c++ classes and copying objects to another object

我有一个机器人 class,它有一个整数指针向量(用于存储完成的工作历史记录),但是当我将一个机器人的对象复制到另一个机器人并且第一个机器人超出范围时,并且然后我打印机器人的历史,它给了我一大堆随机数。我已经尝试制作自己的复制构造函数并将 _history 逐个值设置为新对象 _history,但给出了相同的响应。

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;
}

主要

#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;
}


输出我得到:

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)

当你摧毁一个机器人时,你就摧毁了它的工作历史。 当你复制一个机器人时会发生什么,它会得到一个指向工作历史的指针的副本。换句话说,第二个机器人有一个指向与第一个机器人创建的完全相同的整数向量的指针。

现在当第一个机器人被销毁时,它会删除它拥有的工作历史。这就是为什么打印第二个机器人的工作历史是无效的:那个内存已经被释放了。

我可以就此提出两种可能的解决方案。一种是实施“5 条规则”,除其他事项外,它允许您指定(通过定义 复制构造函数 赋值运算符 ) 一个机器人如何复制另一个机器人,包括创建一个它拥有的工作历史,并且不能被第一个机器人删除。另一种是使用“共享指针”来管理工作历史的生命周期。

鉴于工作经历听起来不应该被多个机器人共享,我会选择第一个选项。

r2 = r1;

使用隐式声明的默认复制赋值运算符。由于默认实现只是简单地进行成员复制,旧的 _history 指针被简单地覆盖,并且在赋值未被正确释放之前,除了存储在 r2 中的旧向量之外,向量意外地由 2 个对象拥有.

您应该实现移动构造函数 + 移动赋值运算符、复制构造函数 + 复制赋值运算符或两者。

如果您将 _history 向量保持为原始指针,则只需要这样做;更改为 std::vector<int> constructors/assignment 运算符的默认实现存在并且有效,更改为 std::unique_ptr<std::vector<int>> 将导致复制赋值 operator/copy 构造函数被删除。

注意:对于所有方法,您应该按照最后一个选项中的描述更改 whoAmIgetHistory 的 return 类型。

复制赋值运算符

这会在赋值后保持两个对象“完好无损”。

需要一个自定义实现来正确复制指针。

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;
    }
    ...
};

移动作业

这需要您将赋值更改为 r2 = std::move(r1);,使 r1 处于只有析构函数可以保证工作的状态。请注意,这样做你不应该在析构函数中打印 name,因为 r2 的名字已经从

中移走了。
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;
    }
    ...
};

简单选项(推荐)

使用 std::vector<int> 并使用默认构造函数:

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;  

      
};

如果历史 returned 需要为 const 对象修改,请考虑制作 _history mutable.

下面是如何实现5的规则中的五个特殊成员函数的一个例子。首先,您的默认构造函数和采用字符串的构造函数可以组合在一起,以便默认构造函数委托给采用字符串的构造函数:

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

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

这是五的法则

// --- 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 ---

当您处理原始 拥有 指针时,您可以使用 std::unique_ptr 免费获得其中的一些。而不是

std::vector<int>* _history;

你做到了:

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

所以 class 变成:

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

五的规则变得更简单了:

// --- 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 ---

您可能还想通过参考 getHistory() 来 return 您的 _history。以下内容适用于原始指针和 unique_ptr 版本,并为您的 class:

提供了更好的界面
const std::vector<int>& getHistory() const { return *_history; };
      std::vector<int>& getHistory()       { return *_history; };