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

Posted

技术标签:

【中文标题】c++ 类中的意外输出并将对象复制到另一个对象【英文标题】:Unexpected output in c++ classes and copying objects to another object 【发布时间】:2022-01-02 06:26:33 【问题描述】:

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

机器人.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;        
;

机器人.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)

【问题讨论】:

复制原始拥有指针很少是一个好主意。为什么不是普通的std::vector&lt;int&gt; 因为我正在尝试练习指针的功能,有没有办法为我的场景修复这个随机输出? 是的,实现the rule of 3/5/0。您可能需要 5 的规则 @f*** 你能提供你的意思的实现吗?对所有这些指针内容仍然很陌生,谢谢。 按照@TedLyngmo 的建议,在线搜索“C++ 五定律”(或在上面的搜索栏中)。无需在此处重复已有的信息。这是 C++ 使用的基础,因此有据可查。 【参考方案1】:

下面是一个例子,说明如何实现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;

所以类变成了:

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() 的引用返回您的_history。以下代码适用于原始指针和unique_ptr 版本,并为您的类提供了更好的接口:

const std::vector<int>& getHistory() const  return *_history; ;
      std::vector<int>& getHistory()        return *_history; ;

【讨论】:

【参考方案2】:

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

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

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

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

【讨论】:

这很有道理,谢谢,我尝试定义一个复制构造函数并将值一个一个地添加到_history,但仍然出现同样的错误,你能提供一个基本的实现吗?谢谢。 抱歉,我没有仔细查看您的代码。您正在使用赋值运算符来复制机器人,而不是复制构造函数。规则 3 和规则 5 表示,如果您要定义其中之一,则应定义所有这些,以确保涵盖所有基础。定义复制构造函数并没有解决您的问题这一事实就是为什么规则如此有用的一个例子【参考方案3】:
r2 = r1;

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

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

如果您将_history 向量保留为原始指针,则只需要这样做;更改为 std::vector&lt;int&gt; 构造函数/赋值运算符的默认实现存在并且正在工作,更改为 std::unique_ptr&lt;std::vector&lt;int&gt;&gt; 将导致复制赋值运算符/复制构造函数被删除。

注意:对于所有方法,您都应更改 whoAmIgetHistory 的返回类型,如最后一个选项中所述。

复制赋值运算符

这使两个对象在分配后保持“完整”。

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

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&lt;int&gt; 并使用默认构造函数:

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;  

      
;

如果返回的历史记录需要对 const 对象进行修改,请考虑使用 _history mutable

【讨论】:

以上是关于c++ 类中的意外输出并将对象复制到另一个对象的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中将一个对象从一个向量复制到另一个向量?

如何使用可重复对象中的嵌套必填字段将数据从一个表复制到另一个表

从向量中保存的类对象输出类函数

当我在 C++ 中派生一个类时,它是不是会创建一个基类对象并将其作为我的成员变量存储在派生类中?

Actionscript 3 对象相交时意外修改类属性

C++ 将对象向量中的元素复制到具有此元素的向量中