我有问题。我需要克隆包含指针的对象类。问题的一个示例在以下代码中:
#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
的问题。在p1
和p2
上克隆对象p3
和p4
时,内存地址p1
和p3
不同,但m_p
地址是一样的。显然,删除p1
时,p3
删除失败。 p2
和p4
相同。
如何克隆CPoint类对象?
答案 0 :(得分:5)
您似乎正在将其他 Java like 语言的规则应用于C ++ 这是一个根本问题,从长远来看会导致各种各样的问题。
你需要学习C ++的习语。
在C ++中,您希望使用C ++字符串(std :: string)而不是C-String接口。
#include <string.h> // C-Interface
// What you really want
#include <string> // C++ Interface
如果你的类包含指针,那么你可能做错了。 RAW指针应包装在智能指针(或容器)中,以正确控制其寿命。如果你把一个指针放到一个商业类中,你就会破坏关注点分离原则。
class CPoint
{
protected:
int m_x;
int m_y;
int *m_p; // What is it supposed to be?
// Who owns it?
由于你的班级有一个指针,它打破了三个规则 如果你想管理这个类中的指针(你没有(打破关注点分离))那么你应该实现三个规则(C ++ 11中的五条规则)(查找它)。如果您想了解RAW指针在这里的处理方式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)
但如果将RAW指针正确地包装在适当的类中,则不需要这样做。编译器生成的这些方法的默认版本可以正常工作。
完全破坏封装的好方法。
int getX();
int getY();
void setX(int x);
void setY(int y);
串!船尾。你真正想要的是序列化方法。
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;
}
答案 1 :(得分:1)
除了对立即数据成员m_x
和m_y
进行浅层复制外,还需要深度复制指针成员m_p
。由于您尚未显示此类的构造函数或m_p
实际指向的构造函数,因此我将假设m_p
指向int
数组的第一个元素。深度复制这需要:
int
的新数组,其大小与原始数组相同(或更大)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;
}
关于您的代码的一些注意事项:
vector<int>
而不是指向int
数组的原始指针。 vector<int>
,这很容易 - 只需拨打vecctor<int>::size()
即可。你需要知道数组的大小,以便制作它的副本。显然。clone()
类型函数通常仅在通过基类指针制作多态对象的副本时才有用。由于您的课程及其使用不属于此类别,因此clone()
功能不是正确的方法。考虑使用复制构造函数和复制赋值运算符,并且不要忘记也实现析构函数。更好的是,完全避免所有这些内容并遵循Rule of Zero。答案 2 :(得分:1)
在此示例中是整数,但可以是任何类型。我遇到的问题是克隆一个包含指向另一种类型的指针的对象。
我相信这里基本上有两种情况:你希望包含对象拥有指向对象;或者您不希望包含对象拥有指向的对象。
让我们从非拥有开始吧。 C ++提供什么工具来表示非拥有指针?好吧,常规指针是非拥有的。你如何复制常规指针?你什么都不做。你让编译器处理它,生成你可以随意使用的正确的拷贝构造函数(当你在它时,让编译器生成一个析构函数)。
拥有怎么样?拥有指针的工具是什么?好吧,对于大多数情况你甚至不需要指针:直接存储一个值,再次让编译器生成正确的复制构造函数(以及析构函数!)。在提供的示例int m_p;
可以很好地工作。
当涉及多态基类时,这种情况会令人烦恼:复制可能会导致切片。 C ++是否为这种情况提供了工具?可悲的是,事实并非如此。你必须手工编写。但请帮自己一个忙,不要将这些问题与课堂上的其他人混在一起(单一责任原则)。
编写一个可重用的类(奖励点:使其成为模板)拥有单个指针,在销毁时清除它,并在复制构造函数中执行多态复制(常见的习惯用法涉及虚拟克隆函数)。然后在您的CPoint
中放置一个可重复使用的类的值,然后......你猜对了!让编译器生成正确的拷贝构造函数。
答案 3 :(得分:0)
你必须问自己:你的对象的每个实例“拥有”它指向的对象,还是它们都引用了其他东西所拥有的共同对象?
如果您拥有所有权,则每个实例都必须指向单个副本。这意味着您不必复制指针,您必须创建它指向的对象的克隆,并将此新对象分配给副本的指针。
答案 4 :(得分:0)
您必须为CPoint
中的所有指针重新分配内存,并将其数据复制到新内存中。在您的情况下,您必须执行以下操作:
CPoint clone()
{
CPoint p;
p = *this;
p.m_p = new int();
*p.m_p = *m_p;
return p;
}
答案 5 :(得分:-1)
假设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;
}
请注意,这样的成员指针的唯一目的是添加具有NULL值的额外可能性 - 这可能具有单独的含义。
另请注意,必须执行以下操作以避免内存泄漏和堆损坏: