如何克隆包含指针的对象

How to clone objects containing pointers?

本文关键字:对象 指针 包含 何克隆      更新时间:2023-10-16

我有问题。我需要克隆包含指针的对象类。该问题的一个示例是在以下代码中:

#include "stdafx.h"
#include <iostream>
#include <string.h>
#include <vector>
class CPoint
{
protected:
    int m_x;
    int m_y;
    int *m_p;
public:
    CPoint();
    CPoint(int x, int y);
    ~CPoint();
    CPoint*         clone();
    static CPoint*  clone(CPoint& p);
    int getX();
    int getY();
    void setX(int x);
    void setY(int y);
    void toString();
};
int CPoint::getX()
{
    return m_x;
}
int CPoint::getY()
{
    return m_y;
}
void CPoint::setX( int x )
{
    m_x = x;
}
void CPoint::setY( int y )
{
    m_y = y;
}
void CPoint::toString()
{
    std::cout << "(" << m_x << ", " << m_y<< ", " << *m_p << ")" << std::endl;
}
CPoint::CPoint( int x, int y )
{
    m_x = x;
    m_y = y;    
    m_p = new int();
    *m_p = x + y;
}
CPoint::CPoint()
{
    m_p = new int();
    *m_p = 1000;
}
CPoint* CPoint::clone()
{
    CPoint *p = new CPoint();
    *p = *this;
    return p;
}
CPoint* CPoint::clone( CPoint& p )
{
    CPoint *q = new CPoint();
    *q = p;
    return q;
}
CPoint::~CPoint()
{
    if (m_p) {
        delete m_p;
        m_p = NULL;
    }
}
int _tmain(int argc, _TCHAR* argv[])
{
    CPoint *p1 = new CPoint(10, 20);
    CPoint *p2 = new CPoint(30, 40);
    p1->toString();
    p2->toString();
    CPoint *p3;
    p3 = CPoint::clone(*p1);
    p3->toString();
    CPoint *p4;
    p4 = p2->clone();
    p4->toString();
    p1->setX(50);
    p1->setY(60);
    p2->setX(80);
    p2->setY(90);
    p3->toString();
    p4->toString();
    delete p1;
    delete p2;
    delete p3;
    delete p4;
    int a;
    std::cin >> a;
    return 0;
}

我与变量m_p的问题。当p3p4上的克隆对象p1p2时,内存地址p1p3不同,但m_p地址是相同的。显然,当删除p1时,p3删除失败。使用p2p4相同。

我如何克隆cpoint class对象?

您似乎正在应用某些其他 java的规则,例如C 的语言。
从长远来看,这是一个基本问题,将导致各种问题。

您需要学习C 的成语。

在C 中,您要使用C 字符串(STD :: String)而不是C-string接口。

#include <string.h>   // C-Interface
// What you really want
#include <string>     // C++ Interface

如果您的班级包含一个指针,那么您可能做错了什么。原始指针应包裹在智能指针(或容器)中,以正确控制其寿命。如果您将指针放入商务舱,您将打破关注原则的分离。

class CPoint
{
    protected:
        int m_x;
        int m_y;
        int *m_p;  // What is it supposed to be?
                   // Who owns it?

由于您的班级有指针,因此打破了三个规则。
如果您想在此类中管理指针(并且不打破关注点的分离),那么您应该已经实施了三个规则(C 11中的五个规则)(查找它)。如果您想了解如何处理原始指针外观https://stackoverflow.com/a/1846409/14065

不需要克隆方法。这就是复制构造函数的目的。您不是在编写需要克隆的课程(否则它将具有毒性破坏者)。您的班级不是多态性的,不会源自。因此,复制构造函数将完美工作。

CPoint*         clone();
static CPoint*  clone(CPoint& p);
// Copy constructor looks like this:
CPoint(CPoint const& rjs)
// Assignment operator looks like this:
CPoint& operator=(CPoint& rhs)

,如果正确地将原始指针包装在适当的类中,则不需要这一点。这些方法的编译器生成的默认版本将正常工作。

完全破坏封装的好方法。

int getX();
int getY();
void setX(int x);
void setY(int y);

string!船尾。您真正想要的是序列化方法。

void toString();
// serializer look like this:
friend std::ostream& operator<<(std::ostream& stream, CPoint const& data)
{
     // Convert CPoint (data) to the stream.
     return stream;
}

在C 中,我们不会动态创建对象,除非我们需要。
在这里您不需要。创建本地对象效果更好,因为即使存在异常,也可以保证它们的寿命。

// Rather than dynamically creating them
CPoint *p1 = new CPoint(10, 20);
CPoint *p2 = new CPoint(30, 40);
// Just declare two local variables:
CPoint  p1 = CPoint(10, 20);
CPoint  p2(30, 40);           // Alternative to the above but means the same.
// Much better to use operator<<
// Also shows the functions are badly named. You are not converting to string.
// but rather printing them to a stream.
p1->toString();
p2->toString();
std::cout << p1;
myFileStream << p2;  // allows you to easily specify the actual stream.

复制构造函数可以更好地复制对象

CPoint *p3;
p3 = CPoint::clone(*p1);
// If we were still using pointers. 
CPoint* p3 = new CPoint(p1);
// But much nicer to not even use pointers
CPoint  p3(p1);

如果您看到手动调用以在功能中删除的手动调用,通常是一个设计错误。

delete p1;
delete p2;
delete p3;
delete p4;

如果您的指针将其包裹在智能指针(或容器)中的类中,则可以安全使用它们。这是因为对于局部对象,保证驱动器被调用,因此当您的对象不在范围内时,您的对象将正确删除指针。目前,此代码并不是例外,如果异常传播通过它们,将泄漏。

小注意:main()很特别。如果您没有为您指定编译器工厂return 0;的返回值。如果您的应用程序没有错误状态最好将此功能用作向其他开发人员的标志,则您的代码将始终干净地退出。

return 0;

我会像这样重写:

#include <iostream>
#include <string>
#include <vector>
class CPoint
{
protected:
    int m_x;
    int m_y;
    std::vector<int> m_p;
public:
    // If you don't explicitly initialize m_x and m_y them
    // they will have indeterminate (random) values.
    CPoint()             : m_x(0), m_y(0) {m_p.push_back(1000);}
    CPoint(int x, int y) : m_x(x), m_y(y) {m_p.push_back(x + y);}
    int getX()        { return m_x;}
    int getY()        { return m_y;}
    void setX(int x)  { m_x = x;}
    void setY(int y)  { m_y = y;}
    friend std::ostream& operator<<(std::ostream& stream, CPoint const& d)
    {
        return stream << "(" << d.m_x << ", " << d.m_y<< ", " << d.m_p[0] << ")" << std::endl;
    }
};


int main(int argc, char* argv[])
{
    CPoint p1(10, 20);
    CPoint p2(30, 40);
    std::cout << p1 << p2;
    CPoint p3(p1);
    std::cout << p3;
    CPoint p4(p2);
    std::cout << p4;
    p1.setX(50);
    p1.setY(60);
    p2.setX(80);
    p2.setY(90);
    std::cout << p1 << p2 << p3 << p4;
    int a;
    std::cin >> a;
}

在此示例中是一个整数,但可能是任何类型。我的问题是要克隆一个包含指向另一种类型指针的对象。

我相信这里基本上有两种情况:您希望包含对象拥有指向对象;或者您不希望包含对象拥有尖头对象。

让我们从非所有者开始。C 为代表非持有指针提供的工具是什么?好吧,常规指针是非所有人的。以及如何复制常规指针?你什么都不做。您让编译器处理它,生成可以随意使用的正确复制构造函数(当您使用时,让编译器也会生成destructor)。

拥有?拥有指针的工具是什么?好吧,在大多数情况下,您甚至不需要指针:直接存储一个值,然后再一次让编译器生成正确的复制构造函数(也是击曲线!)。在提供的int m_p;中,在示例中可以很好地工作。

在涉及多态基类别的情况下,这种情况会引起烦恼:复制可能会导致切片。C 是否为这种情况提供了工具?可悲的是,事实并非如此。您必须手工写它。但是请自己帮个忙,不要将这些关注点与班级的其余部分(单一责任原则)相结合。

编写一个可重复使用的类(奖励点:使其成为模板),该类拥有一个指针,将其清理为破坏,并在复制构造器中执行多态副本(一个常见的成语涉及虚拟克隆函数)。然后在CPoint中放置该可重复使用的类的值,然后...您猜对了!让编译器生成正确的复制构造函数。

除了要浅层数据成员m_xm_y外,您还需要对指针成员m_p进行深入浏览。由于您尚未显示此类的构造函数或m_p真正指向的构造函数,因此我将假设m_p点指向int数组的第一个元素。深入复印需要:

  1. 实例化一个与原始数组相同(或更大)大小的int的新数组
  2. 将每个元素从原始数组复制到新数组
  3. 在克隆对象中设置m_p,以指向此新数组的第一个元素

如何完成此操作:

CPoint* CPoint::clone(CPoint& rhs)
{
  CPoint* ret = new CPoint;
  ret->m_x = rhs.m_x;
  ret->m_y = rhs.m_y;
  size_t m_p_count = /* somehow determine the size of rhs.m_p */;
  ret->m_p = new int[m_p_count];
  std::copy(&rhs.m_p[0], &rhs.m_p[m_p_count], ret->m_p);
  return ret;
}

有关您的代码的一些注释:

  1. 您最好使用vector<int>而不是将int数组的原始指针。
  2. 禁止#1,您应该使用智能指针而不是原始指针
  3. 我在上面的代码中看不到确定数组大小的任何方式。如果您使用vector<int>-只需致电vecctor<int>::size(),这将很容易。您需要知道数组的大小,以便制作它的副本。
  4. clone()类型函数通常仅在通过基类指针制作多态性对象的副本时才有用。由于您的班级和您的使用情况不属于此类别,因此clone()功能并不是首先进行的正确方法。考虑使用复制构造函数和复制分配运算符,而不要忘记也实现驱动器。更好的是,完全避免所有这些东西,并遵循零的规则。

您必须为CPoint中的所有指针重新分配内存,然后将其数据复制到新的内存中。在您的情况下,您必须执行操作之后:

CPoint clone()
{
   CPoint p;
   p = *this;
   p.m_p = new int();
   *p.m_p = *m_p;
   return p;
}

您必须问自己:您对象的每个实例是否"拥有"它指向的对象,或者它们都指的是由其他事物拥有的公共对象?

当您拥有所有权案例时,每个实例都必须指向单个副本。这意味着您不必复制指针,就必须创建指向其指向的对象的克隆,并将此新对象分配给副本的指针。

假设M_P仅指向一个整数(而不是整个数组),可以这样进行克隆:

CPoint* CPoint::clone()
{
    CPoint* cloned = new CPoint(m_x, m_y);
    if (m_p)
    {
        cloned->m_p = new int;
        *cloned->m_p = *m_p;
    }
    return cloned;
}

请注意,这样的成员指针的唯一目的是添加具有空价值的其他可能性 - 可以具有独立的含义。

还请注意,必须执行以下操作以避免内存泄漏和堆损坏:

  • 复制构造函数和分配运算符必须"禁用"(已声明为私有)
  • 驱动器必须删除m_p。