如果对象A包含成员对象B而对象B有指向其父对象A的指针,是否需要为对象B指定复制构造函数?
假设没有动态分配。
此外,3规则适用于此吗?
答案 0 :(得分:3)
您的设计通过双向导航实现合成。这完全有效 然而,正如谢尔盖在评论中指出的那样,这样的设计并非没有问题。
假设您有一个包含Object
的课程Container
和一个课程Object
。这里有一些基本问题:
Container c;
Object mo1; // Q1: should this be valid ? (i.e. is an object without parent allowed
Object mo2 = c.o; // Q2: is this acceptable ? Q3: Who is parent of mo2 ?
看看问题Q2和Q3:如果这样的初始化是可以接受的,那么直接的问题是你想要的父母:
mo2
没有父级,则需要根据3规则的复制构造函数来清除父级。 mo2
应该引用同一个父级(尽管它不是成员),则可以保留默认的复制构造函数。 示例:
struct Container;
struct Object{
Container *parent;
Object (Container *p=nullptr) : parent(p) { cout << "construct object"<<endl; }
// copy constructor or compiler generated one depending on Q2+Q3
~Object() {}
...
};
struct Container {
Object o;
Container() : o(this) {}
};
如果不能接受这样的初始化,则应该在代码中明确禁止复制构造。
Object (const Object &o) = delete;
重要说明: Container
可能还需要复制构造函数。无论您对Object
做出什么决定,如果必须是可复制的,您可能必须在Container
处理。你绝不可以在那里使用Object
的复制构造函数。
重要提示2: Container
可能会在更复杂的情况下使用。以vector<Container>
为例。将新容器添加到向量时,可能会执行重定位,如果您没有提供注意的Container复制构造函数,则可能会使指针无效!
答案 1 :(得分:0)
是的,你应该考虑实现一个复制构造函数,即使子对象没有管理任何句柄或指向资源的指针(如果没有这样的构造函数会盲目地与副本共享导致熟悉的问题)理解)。
原因是父母的孩子的副本不一定被视为父母的孩子!
如果孩子的副本被认为是在同一个父母的同一个,并且父母有一个孩子的列表,他们的责任是插入该列表?它必须在某个地方完成。如果在复制构造函数中没有完成,那么代码中执行复制的每个位置都必须记住采取一些额外的操作,例如copy.register_with_parent()
。任何遗忘的地方都可能是一个错误。
如果不认为子项的副本位于同一父项下,则无论如何,一个天真的成员成员副本都将具有父指针。使用指向父实际上不是父代的指针的对象是什么?有些东西必须清除指针,或者将它设置为所有孤立子对象的默认父对象&#34;管他呢。这将在复制构造函数中完成。
如果您不知道这些问题的答案,那么请定义一个未实现的复制构造函数,以便在您确实知道该行为应该是什么之前不会进行复制。所以,再次复制构造函数,尽管已声明但未定义!
答案 2 :(得分:-1)
哦,但除非使用指针分配,否则父对象不能拥有它的被动类对象:
class B;
class A
{
B son;
};
class B : public A
{
A* parent;
};
结果:
field 'son' has incomplete type 'B'
B son;
^
然后,如果您使用分配:
class B;
class A
{
public:
A(void);
~A();
private:
B* son;
};
class B : public A
{
public:
B() {} // <-depends what you want to archieve
private:
A* parent;
};
A::A(void) : son(new B) {}
A::~A() {delete son;}
此时出现了问题。您希望父指针指向全局对象A
,分配它自己的A
对象,还是指向this
?
全局变量 - 无需编写复制构造函数:
// A class not changed
A global;
class B : public A
{
public:
B() {parent = &global;}
...
如果object分配了它自己的A
对象,那么你需要坚持3(或4)的规则。此外,您的班级A
也需要遵守此规则,因为我们将复制父对象,因此每个B
对象都拥有它自己:
class B;
class A
{
public:
A(void);
A(const A& other);
A& operator=(const A& other);
~A();
private:
B* son;
};
class B : public A
{
public:
B() : parent(new A) {}
B(const B& other) : parent(new A(*other.parent)) {}
B& operator=(const B& other);
~B() {delete parent;}
private:
A* parent;
};
A::A(void) : son(new B) {}
A::A(const A& other) : son(new B(*other.son)) {}
A& A::operator=(const A& other)
{
delete son;
son = new B(*other.son);
return *this;
}
B& B::operator=(const B& other)
{
delete parent;
parent = new A(*other.parent);
return *this;
}
A::~A() {delete son;}
最后,对象B
指向同一个对象基类,不需要像全局变量那样特殊的东西:
class B;
class A
{
public:
A(void);
~A();
private:
B* son;
};
class B : public A
{
public:
B() : parent(this) {} // pointless, but yeah, nothing else is needed.
private:
A* parent;
};
A::A(void) : son(new B) {}
A::~A() {delete son;}
这不是应该做什么,不应该做什么。这一切都取决于你想要达到的目标。